diff --git a/DEPS b/DEPS
index ea70baa..ccb3929 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': '9018952290a468886c819405c6d9495b4aa5d7d4',
+  'skia_revision': '78c8f30d6167b4bf40937c6a8814cd448e2228a6',
   # 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': '309a9310a4b080f4cb216ff9c7aaab71869487ad',
+  'v8_revision': '942ddf2c38ef250d149e83cc2fdb53964dc06c0b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -64,7 +64,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'ef73cf5838ab3a902872d9fc57a90621cc3d7f21',
+  'pdfium_revision': 'dc3a87c88da1ac710eadabeb2e5cf01aecb63f4b',
   # 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': '37015fb470d81b7a96a091127590e6e461705059',
+  'catapult_revision': '9e7bc18ce70595be32d8534b4309aff83ac78434',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -196,7 +196,7 @@
     Var('chromium_git') + '/external/bidichecker/lib.git' + '@' + '97f2aa645b74c28c57eca56992235c79850fa9e0',
 
   'src/third_party/webgl/src':
-    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6517159d2016833417582d46a8dfc4e941ac0bd2',
+    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '87aaf44c5333e8b209baf5e11baa325b79ddeb62',
 
   'src/third_party/webdriver/pylib':
     Var('chromium_git') + '/external/selenium/py.git' + '@' + '5fd78261a75fe08d27ca4835fb6c5ce4b42275bd',
@@ -232,7 +232,7 @@
     Var('chromium_git') + '/native_client/src/third_party/scons-2.0.1.git' + '@' + '1c1550e17fc26355d08627fbdec13d8291227067',
 
   'src/third_party/webrtc':
-    Var('chromium_git') + '/external/webrtc/trunk/webrtc.git' + '@' + '9d4241cbf21ab48686b80918bf5e4d72f2c15574', # commit position 18140
+    Var('chromium_git') + '/external/webrtc/trunk/webrtc.git' + '@' + 'a24bcceea4a66fb3201255809f369b765083004f', # commit position 18147
 
   'src/third_party/openmax_dl':
     Var('chromium_git') + '/external/webrtc/deps/third_party/openmax.git' + '@' +  Var('openmax_dl_revision'),
@@ -403,7 +403,7 @@
 
     # For Linux and Chromium OS.
     'src/third_party/cros_system_api':
-      Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + '2a19709e6f2e52d8e8e276b63e46a0ff31020d57',
+      Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + '6b09df23f8a968fc72956218a62302b89d5334b1',
 
     # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
     'src/third_party/chromite':
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
index 29e96a87..0350cf6 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
@@ -32,8 +32,8 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.parameter.ParameterizedTest;
 import org.chromium.content.browser.BindingManager;
+import org.chromium.content.browser.ChildProcessConnection;
 import org.chromium.content.browser.ChildProcessLauncher;
-import org.chromium.content.browser.ManagedChildProcessConnection;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.net.test.util.TestWebServer;
@@ -585,7 +585,7 @@
         }
 
         @Override
-        public void addNewConnection(int pid, ManagedChildProcessConnection connection) {
+        public void addNewConnection(int pid, ChildProcessConnection connection) {
             mIsChildProcessCreated = true;
         }
 
diff --git a/base/metrics/persistent_histogram_allocator.cc b/base/metrics/persistent_histogram_allocator.cc
index ea1a2356..e23c635 100644
--- a/base/metrics/persistent_histogram_allocator.cc
+++ b/base/metrics/persistent_histogram_allocator.cc
@@ -610,7 +610,7 @@
 
   size_t counts_bytes = CalculateRequiredCountsBytes(histogram_bucket_count);
   PersistentMemoryAllocator::Reference counts_ref =
-      subtle::NoBarrier_Load(&histogram_data_ptr->counts_ref);
+      subtle::Acquire_Load(&histogram_data_ptr->counts_ref);
   if (counts_bytes == 0 ||
       (counts_ref != 0 &&
        memory_allocator_->GetAllocSize(counts_ref) < counts_bytes)) {
diff --git a/base/metrics/persistent_memory_allocator.cc b/base/metrics/persistent_memory_allocator.cc
index 3c5198a..deb82ac 100644
--- a/base/metrics/persistent_memory_allocator.cc
+++ b/base/metrics/persistent_memory_allocator.cc
@@ -1133,7 +1133,7 @@
 void* DelayedPersistentAllocation::Get() const {
   // Relaxed operations are acceptable here because it's not protecting the
   // contents of the allocation in any way.
-  Reference ref = reference_->load(std::memory_order_relaxed);
+  Reference ref = reference_->load(std::memory_order_acquire);
   if (!ref) {
     ref = allocator_->Allocate(size_, type_);
     if (!ref)
@@ -1144,7 +1144,7 @@
     // cannot be retried.
     Reference existing = 0;  // Must be mutable; receives actual value.
     if (reference_->compare_exchange_strong(existing, ref,
-                                            std::memory_order_relaxed,
+                                            std::memory_order_release,
                                             std::memory_order_relaxed)) {
       if (make_iterable_)
         allocator_->MakeIterable(ref);
diff --git a/base/metrics/persistent_memory_allocator.h b/base/metrics/persistent_memory_allocator.h
index b931b2f..b7e7bd08 100644
--- a/base/metrics/persistent_memory_allocator.h
+++ b/base/metrics/persistent_memory_allocator.h
@@ -793,7 +793,8 @@
   // Once allocated, a reference to the segment will be stored at |ref|.
   // This shared location must be initialized to zero (0); it is checked
   // with every Get() request to see if the allocation has already been
-  // done.
+  // done. If reading |ref| outside of this object, be sure to do an
+  // "acquire" load. Don't write to it -- leave that to this object.
   //
   // For convenience, methods taking both Atomic32 and std::atomic<Reference>
   // are defined.
diff --git a/build/args/headless.gn b/build/args/headless.gn
index 639c4f7..95979fc0 100644
--- a/build/args/headless.gn
+++ b/build/args/headless.gn
@@ -15,6 +15,9 @@
 # Embed resource.pak into binary to simplify deployment.
 headless_use_embedded_resources = true
 
+# Expose headless bindings for freetype library bundled with Chromium.
+headless_fontconfig_utils = true
+
 # In order to simplify deployment we build ICU data file
 # into binary.
 icu_use_data_file = false
diff --git a/cc/layers/picture_layer_unittest.cc b/cc/layers/picture_layer_unittest.cc
index 203c8dc..f976ab9f 100644
--- a/cc/layers/picture_layer_unittest.cc
+++ b/cc/layers/picture_layer_unittest.cc
@@ -308,7 +308,6 @@
 
   auto animation_host2 = AnimationHost::CreateForTesting(ThreadInstance::MAIN);
 
-  // TODO(sad): InitParams will be movable.
   LayerTreeHost::InitParams params2;
   params2.client = &host_client1;
   params2.settings = &settings;
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 3919895..ea46b47 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -67,8 +67,6 @@
 class CC_EXPORT LayerTreeHost : public NON_EXPORTED_BASE(SurfaceReferenceOwner),
                                 public NON_EXPORTED_BASE(MutatorHostClient) {
  public:
-  // TODO(sad): InitParams should be a movable type so that it can be
-  // std::move()d to the Create* functions.
   struct CC_EXPORT InitParams {
     LayerTreeHostClient* client = nullptr;
     TaskGraphRunner* task_graph_runner = nullptr;
diff --git a/chrome/android/java/res/layout/download_manager_spinner_drop_down.xml b/chrome/android/java/res/layout/download_manager_spinner_drop_down.xml
index bbd10e35..d93c24b 100644
--- a/chrome/android/java/res/layout/download_manager_spinner_drop_down.xml
+++ b/chrome/android/java/res/layout/download_manager_spinner_drop_down.xml
@@ -8,6 +8,7 @@
     android:layout_height="wrap_content"
     android:background="?android:attr/selectableItemBackground"
     android:gravity="center_vertical"
+    android:drawablePadding="16dp"
     android:minWidth="176dp"
     android:minHeight="48dp"
     android:paddingStart="16dp"
diff --git a/chrome/android/java/res/menu/download_manager_menu.xml b/chrome/android/java/res/menu/download_manager_menu.xml
index 313c5ea..79f19ba 100644
--- a/chrome/android/java/res/menu/download_manager_menu.xml
+++ b/chrome/android/java/res/menu/download_manager_menu.xml
@@ -8,6 +8,12 @@
 
     <group android:id="@+id/normal_menu_group" >
         <item
+            android:id="@+id/info_menu_id"
+            android:icon="@drawable/btn_info"
+            android:title="@string/show_info"
+            android:visible="false"
+            chrome:showAsAction="ifRoom" />
+        <item
             android:id="@+id/search_menu_id"
             android:icon="@drawable/ic_search"
             android:title="@string/search"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index d2cb332f..79ae686 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -168,6 +168,7 @@
     // Android.
     public static final String DATA_REDUCTION_MAIN_MENU = "DataReductionProxyMainMenu";
     public static final String DATA_REDUCTION_SITE_BREAKDOWN = "DataReductionProxySiteBreakdown";
+    public static final String DOWNLOAD_HOME_SHOW_STORAGE_INFO = "DownloadHomeShowStorageInfo";
     // When enabled, fullscreen WebContents will be moved to a new Activity. Coming soon...
     public static final String FULLSCREEN_ACTIVITY = "FullscreenActivity";
     // Whether we show an important sites dialog in the "Clear Browsing Data" flow.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
index 58e0a44..ca02f02 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
@@ -533,6 +533,16 @@
         return super.getPanelState();
     }
 
+    @Override
+    public void requestPanelShow(StateChangeReason reason) {
+        // If a re-tap is causing the panel to show when already shown, the superclass may ignore
+        // that, but we want to be sure to capture search metrics for each tap.
+        if (isShowing() && getPanelState() == PanelState.PEEKED) {
+            peekPanel(reason);
+        }
+        super.requestPanelShow(reason);
+    }
+
     /**
      * Gets whether a touch on the content view has been done yet or not.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java
index ffd9d6e..c51fdb5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java
@@ -11,10 +11,12 @@
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.ObserverList;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.download.DownloadItem;
 import org.chromium.chrome.browser.download.DownloadSharedPreferenceHelper;
 import org.chromium.chrome.browser.download.DownloadUtils;
@@ -141,6 +143,9 @@
 
     private static final String EMPTY_QUERY = null;
 
+    private static final String PREF_SHOW_STORAGE_INFO_HEADER =
+            "download_home_show_storage_info_header";
+
     private final BackendItems mRegularDownloadItems = new BackendItemsImpl();
     private final BackendItems mIncognitoDownloadItems = new BackendItemsImpl();
     private final BackendItems mOfflinePageItems = new BackendItemsImpl();
@@ -161,6 +166,7 @@
     private String mSearchQuery = EMPTY_QUERY;
     private SpaceDisplay mSpaceDisplay;
     private boolean mIsSearching;
+    private boolean mShouldShowStorageInfoHeader;
 
     @Nullable // This may be null during tests.
     private UiConfig mUiConfig;
@@ -196,6 +202,9 @@
         initializeOfflinePageBridge();
 
         sDeletedFileTracker.incrementInstanceCount();
+        mShouldShowStorageInfoHeader = ContextUtils.getAppSharedPreferences().getBoolean(
+                PREF_SHOW_STORAGE_INFO_HEADER,
+                ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_HOME_SHOW_STORAGE_INFO));
     }
 
     /** Called when the user's regular or incognito download history has been loaded. */
@@ -531,6 +540,26 @@
         filter(mFilter);
     }
 
+    /** @return Whether the storage info header should be visible. */
+    boolean shouldShowStorageInfoHeader() {
+        return mShouldShowStorageInfoHeader;
+    }
+
+    /**
+     * Sets the visibility of the storage info header and saves user selection to shared preference.
+     * @param show Whether or not we should show the storage info header.
+     */
+    void setShowStorageInfoHeader(boolean show) {
+        mShouldShowStorageInfoHeader = show;
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .putBoolean(PREF_SHOW_STORAGE_INFO_HEADER, mShouldShowStorageInfoHeader)
+                .apply();
+        RecordHistogram.recordBooleanHistogram(
+                "Android.DownloadManager.ShowStorageInfo", mShouldShowStorageInfoHeader);
+        filter(mFilter);
+    }
+
     private DownloadDelegate getDownloadDelegate() {
         return mBackendProvider.getDownloadDelegate();
     }
@@ -560,7 +589,10 @@
         }
 
         clear(false);
-        if (!filteredTimedItems.isEmpty() && !mIsSearching) addHeader();
+        if (!filteredTimedItems.isEmpty() && !mIsSearching && mShouldShowStorageInfoHeader) {
+            addHeader();
+        }
+
         loadItems(filteredTimedItems);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadManagerToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadManagerToolbar.java
index 5ed4974..9006d97 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadManagerToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadManagerToolbar.java
@@ -85,7 +85,15 @@
     }
 
     @Override
-    public void onManagerDestroyed() { }
+    protected void onDataChanged(int numItems) {
+        super.onDataChanged(numItems);
+        getMenu().findItem(R.id.info_menu_id).setVisible(numItems > 0);
+    }
+
+    @Override
+    public void onManagerDestroyed() {
+        mSpinner.setAdapter(null);
+    }
 
     @Override
     public void showSearchView() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadManagerUi.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadManagerUi.java
index 031a6f2..15ca52f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadManagerUi.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadManagerUi.java
@@ -8,6 +8,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.support.graphics.drawable.VectorDrawableCompat;
 import android.support.v7.widget.RecyclerView;
@@ -31,6 +32,7 @@
 import org.chromium.chrome.browser.snackbar.Snackbar;
 import org.chromium.chrome.browser.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController;
+import org.chromium.chrome.browser.widget.TintedDrawable;
 import org.chromium.chrome.browser.widget.selection.SelectableListLayout;
 import org.chromium.chrome.browser.widget.selection.SelectableListToolbar;
 import org.chromium.chrome.browser.widget.selection.SelectableListToolbar.SearchDelegate;
@@ -221,6 +223,7 @@
         addObserver(mHistoryAdapter);
 
         mUndoDeletionSnackbarController = new UndoDeletionSnackbarController();
+        enableStorageInfoHeader(mHistoryAdapter.shouldShowStorageInfoHeader());
 
         mIsSeparateActivity = isSeparateActivity;
         if (!mIsSeparateActivity) mToolbar.removeCloseButton();
@@ -302,6 +305,9 @@
         } else if (item.getItemId() == R.id.selection_mode_share_menu_id) {
             shareSelectedItems();
             return true;
+        } else if (item.getItemId() == R.id.info_menu_id) {
+            enableStorageInfoHeader(!mHistoryAdapter.shouldShowStorageInfoHeader());
+            return true;
         } else if (item.getItemId() == R.id.search_menu_id) {
             // The header should be removed as soon as a search is started. It will be added back in
             // DownloadHistoryAdatper#filter() when the search is ended.
@@ -385,6 +391,16 @@
         mBackendProvider.getSelectionDelegate().clearSelection();
     }
 
+    private void enableStorageInfoHeader(boolean show) {
+        mHistoryAdapter.setShowStorageInfoHeader(show);
+        MenuItem infoMenuItem = mToolbar.getMenu().findItem(R.id.info_menu_id);
+        Drawable iconDrawable = TintedDrawable.constructTintedDrawable(mActivity.getResources(),
+                R.drawable.btn_info,
+                show ? R.color.light_active_color : R.color.default_text_color);
+        infoMenuItem.setIcon(iconDrawable);
+        infoMenuItem.setTitle(show ? R.string.hide_info : R.string.show_info);
+    }
+
     /**
      * @return An Intent to share the selected items.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/FilterAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/FilterAdapter.java
index 3a155c8d..7b8cf0c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/FilterAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/FilterAdapter.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.download.ui;
 
+import android.graphics.drawable.Drawable;
 import android.support.annotation.LayoutRes;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -14,6 +15,7 @@
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.download.ui.DownloadManagerUi.DownloadUiObserver;
+import org.chromium.chrome.browser.widget.TintedDrawable;
 
 /** An adapter that allows selecting an item from a dropdown spinner. */
 class FilterAdapter
@@ -40,6 +42,11 @@
         TextView labelView =
                 getTextViewFromResource(convertView, R.layout.download_manager_spinner_drop_down);
         labelView.setText(DownloadFilter.getStringIdForFilter(position));
+        int iconId = DownloadFilter.getDrawableForFilter(position);
+        Drawable iconDrawable = TintedDrawable.constructTintedDrawable(
+                mManagerUi.getActivity().getResources(), iconId, R.color.descriptive_text_color);
+        labelView.setCompoundDrawablesWithIntrinsicBounds(iconDrawable, null, null, null);
+
         return labelView;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderFactory.java
index c6794917..9bcc2dc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderFactory.java
@@ -48,6 +48,6 @@
         return new NotificationBuilderForO(context, channelId,
                 new ChannelsInitializer(new NotificationManagerProxyImpl(context.getSystemService(
                                                 NotificationManager.class)),
-                        new ChannelDefinitions()));
+                        context.getResources()));
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxy.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxy.java
index 5070fedc..59e2f5b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxy.java
@@ -6,6 +6,7 @@
 
 import android.app.Notification;
 
+import org.chromium.chrome.browser.notifications.channels.Channel;
 import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 
 import java.util.List;
@@ -14,16 +15,17 @@
  * A proxy for the Android Notification Manager. This allows tests to be written without having to
  * use the real Notification Manager.
  *
- * @see http://developer.android.com/reference/android/app/NotificationManager.html
+ * @see <a href="https://developer.android.com/reference/android/app/NotificationManager.html">
+ *     https://developer.android.com/reference/android/app/NotificationManager.html</a>
  */
 public interface NotificationManagerProxy {
     void cancel(int id);
     void cancel(String tag, int id);
     void cancelAll();
-    void createNotificationChannel(ChannelDefinitions.Channel channel);
+    void createNotificationChannel(Channel channel);
     void createNotificationChannelGroup(ChannelDefinitions.ChannelGroup channelGroup);
-    List<String> getNotificationChannelIds();
-    void deleteNotificationChannel(@ChannelDefinitions.ChannelId String id);
+    List<Channel> getNotificationChannels();
+    void deleteNotificationChannel(String id);
 
     void notify(int id, Notification notification);
     void notify(String tag, int id, Notification notification);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxyImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxyImpl.java
index f9f8e5a..edc8e9c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxyImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxyImpl.java
@@ -4,12 +4,14 @@
 
 package org.chromium.chrome.browser.notifications;
 
+import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.app.NotificationManager;
 
 import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
+import org.chromium.chrome.browser.notifications.channels.Channel;
 import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 
 import java.lang.reflect.Constructor;
@@ -45,16 +47,19 @@
         mNotificationManager.cancelAll();
     }
 
+    @SuppressLint("NewApi")
     @Override
-    public void createNotificationChannel(ChannelDefinitions.Channel channel) {
+    public void createNotificationChannel(Channel channel) {
         assert BuildInfo.isAtLeastO();
         /*
         The code in the try-block uses reflection in order to compile as it calls APIs newer than
         our compileSdkVersion of Android. The equivalent code without reflection looks like this:
 
-            channel.setGroup(channelGroupId);
-            channel.setShowBadge(false);
-            mNotificationManager.createNotificationChannel(channel);
+            NotificationChannel nc = new NotificationChannel(channel.getId(), channel.getName(),
+                    channel.getImportance());
+            nc.setGroup(channel.getGroupId());
+            nc.setShowBadge(false);
+            mNotificationManager.createNotificationChannel(nc);
          */
         // TODO(crbug.com/707804) Stop using reflection once compileSdkVersion is high enough.
         try {
@@ -62,13 +67,12 @@
             Class<?> channelClass = Class.forName("android.app.NotificationChannel");
             Constructor<?> channelConstructor = channelClass.getDeclaredConstructor(
                     String.class, CharSequence.class, int.class);
-            Object channelObject = channelConstructor.newInstance(channel.mId,
-                    ContextUtils.getApplicationContext().getString(channel.mNameResId),
-                    channel.mImportance);
+            Object channelObject = channelConstructor.newInstance(
+                    channel.getId(), channel.getName(), channel.getImportance());
 
             // Set group on channel
             Method setGroupMethod = channelClass.getMethod("setGroup", String.class);
-            setGroupMethod.invoke(channelObject, channel.mGroupId);
+            setGroupMethod.invoke(channelObject, channel.getGroupId());
 
             // Set channel to not badge on app icon
             Method setShowBadgeMethod = channelClass.getMethod("setShowBadge", boolean.class);
@@ -85,6 +89,7 @@
         }
     }
 
+    @SuppressLint("NewApi")
     @Override
     public void createNotificationChannelGroup(ChannelDefinitions.ChannelGroup channelGroup) {
         assert BuildInfo.isAtLeastO();
@@ -114,17 +119,19 @@
         }
     }
 
+    @SuppressLint("NewApi")
     @Override
-    public List<String> getNotificationChannelIds() {
+    public List<Channel> getNotificationChannels() {
         assert BuildInfo.isAtLeastO();
-        List<String> channelIds = new ArrayList<>();
+        List<Channel> channels = new ArrayList<>();
         /*
         The code in the try-block uses reflection in order to compile as it calls APIs newer than
         our compileSdkVersion of Android. The equivalent code without reflection looks like this:
 
             List<NotificationChannel> list = mNotificationManager.getNotificationChannels();
-            for (NotificationChannel channel : list) {
-                channelIds.add(channel.getId());
+            for (NotificationChannel nc : list) {
+                list.add(new Channel(
+                        nc.getId(), nc.getName(), nc.getImportance(), nc.getGroupId()));
             }
          */
         // TODO(crbug.com/707804) Stop using reflection once compileSdkVersion is high enough.
@@ -133,16 +140,24 @@
             List channelsList = (List) method.invoke(mNotificationManager);
             for (Object o : channelsList) {
                 Method getId = o.getClass().getMethod("getId");
-                channelIds.add((String) getId.invoke(o));
+                Method getName = o.getClass().getMethod("getName");
+                Method getImportance = o.getClass().getMethod("getImportance");
+                Method getGroup = o.getClass().getMethod("getGroup");
+                String channelId = (String) getId.invoke(o);
+                String name = (String) getName.invoke(o);
+                int importance = (int) getImportance.invoke(o);
+                String groupId = (String) getGroup.invoke(o);
+                channels.add(new Channel(channelId, name, importance, groupId));
             }
         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
             Log.e(TAG, "Error getting notification channels:", e);
         }
-        return channelIds;
+        return channels;
     }
 
+    @SuppressLint("NewApi")
     @Override
-    public void deleteNotificationChannel(@ChannelDefinitions.ChannelId String id) {
+    public void deleteNotificationChannel(String id) {
         assert BuildInfo.isAtLeastO();
         /*
         The code in the try-block uses reflection in order to compile as it calls APIs newer than
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/Channel.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/Channel.java
new file mode 100644
index 0000000..63d0c8cc
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/Channel.java
@@ -0,0 +1,41 @@
+// 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.notifications.channels;
+
+/**
+ * Helper class that corresponds to the Android NotificationChannel class,
+ * which we cannot use directly until our compileSdkVersion is bumped to O.
+ *
+ * Only the methods & properties we use have been added, others may be added if the need arises.
+ */
+public class Channel {
+    private final String mId;
+    private final CharSequence mName;
+    private final int mImportance;
+    private final String mGroupId;
+
+    public Channel(String id, CharSequence name, int importance, String groupId) {
+        mId = id;
+        mName = name;
+        mImportance = importance;
+        mGroupId = groupId;
+    }
+
+    public String getId() {
+        return mId;
+    }
+
+    public CharSequence getName() {
+        return mName;
+    }
+
+    public int getImportance() {
+        return mImportance;
+    }
+
+    public String getGroupId() {
+        return mGroupId;
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitions.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitions.java
index 81976b4..3744a679 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitions.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitions.java
@@ -6,6 +6,7 @@
 
 import android.annotation.TargetApi;
 import android.app.NotificationManager;
+import android.content.res.Resources;
 import android.os.Build;
 import android.support.annotation.StringDef;
 
@@ -61,27 +62,27 @@
          * incremented every time an entry is modified, removed or added to this map.
          * If an entry is removed from here then it must be added to the LEGACY_CHANNEL_IDs array.
          */
-        static final Map<String, Channel> MAP;
+        static final Map<String, PredefinedChannel> MAP;
         static {
-            Map<String, Channel> map = new HashMap<>();
+            Map<String, PredefinedChannel> map = new HashMap<>();
             map.put(CHANNEL_ID_BROWSER,
-                    new Channel(CHANNEL_ID_BROWSER,
+                    new PredefinedChannel(CHANNEL_ID_BROWSER,
                             org.chromium.chrome.R.string.notification_category_browser,
                             NotificationManager.IMPORTANCE_LOW, CHANNEL_GROUP_ID_GENERAL));
             map.put(CHANNEL_ID_DOWNLOADS,
-                    new Channel(CHANNEL_ID_DOWNLOADS,
+                    new PredefinedChannel(CHANNEL_ID_DOWNLOADS,
                             org.chromium.chrome.R.string.notification_category_downloads,
                             NotificationManager.IMPORTANCE_LOW, CHANNEL_GROUP_ID_GENERAL));
             map.put(CHANNEL_ID_INCOGNITO,
-                    new Channel(CHANNEL_ID_INCOGNITO,
+                    new PredefinedChannel(CHANNEL_ID_INCOGNITO,
                             org.chromium.chrome.R.string.notification_category_incognito,
                             NotificationManager.IMPORTANCE_LOW, CHANNEL_GROUP_ID_GENERAL));
             map.put(CHANNEL_ID_MEDIA,
-                    new Channel(CHANNEL_ID_MEDIA,
+                    new PredefinedChannel(CHANNEL_ID_MEDIA,
                             org.chromium.chrome.R.string.notification_category_media,
                             NotificationManager.IMPORTANCE_LOW, CHANNEL_GROUP_ID_GENERAL));
             map.put(CHANNEL_ID_SITES,
-                    new Channel(CHANNEL_ID_SITES,
+                    new PredefinedChannel(CHANNEL_ID_SITES,
                             org.chromium.chrome.R.string.notification_category_sites,
                             NotificationManager.IMPORTANCE_DEFAULT, CHANNEL_GROUP_ID_GENERAL));
             MAP = Collections.unmodifiableMap(map);
@@ -110,7 +111,7 @@
     /**
      * @return A set of channel ids of channels that should be initialized on startup.
      */
-    Set<String> getStartupChannelIds() {
+    static Set<String> getStartupChannelIds() {
         // CHANNELS_VERSION must be incremented if the set of channels returned here changes.
         return PredefinedChannels.MAP.keySet();
     }
@@ -119,36 +120,42 @@
      * @return An array of old ChannelIds that may have been returned by
      * {@link #getStartupChannelIds} in the past, but are no longer in use.
      */
-    public String[] getLegacyChannelIds() {
+    static String[] getLegacyChannelIds() {
         return LEGACY_CHANNEL_IDS;
     }
 
-    ChannelGroup getChannelGroupFromId(Channel channel) {
+    static ChannelGroup getChannelGroupFromId(PredefinedChannel channel) {
         return PredefinedChannelGroups.MAP.get(channel.mGroupId);
     }
 
-    Channel getChannelFromId(@ChannelId String channelId) {
+    static PredefinedChannel getChannelFromId(@ChannelId String channelId) {
         return PredefinedChannels.MAP.get(channelId);
     }
 
     /**
-     * Helper class containing notification channel properties.
+     * Helper class for storing predefined channel properties while allowing the channel name to be
+     * lazily evaluated only when it is converted to an actual (Notification)Channel.
      */
-    public static class Channel {
+    static class PredefinedChannel {
         @ChannelId
-        public final String mId;
-        public final int mNameResId;
-        public final int mImportance;
+        private final String mId;
+        private final int mNameResId;
+        private final int mImportance;
         @ChannelGroupId
-        public final String mGroupId;
+        private final String mGroupId;
 
-        Channel(@ChannelId String id, int nameResId, int importance,
+        PredefinedChannel(@ChannelId String id, int nameResId, int importance,
                 @ChannelGroupId String groupId) {
             this.mId = id;
             this.mNameResId = nameResId;
             this.mImportance = importance;
             this.mGroupId = groupId;
         }
+
+        Channel toChannel(Resources resources) {
+            String name = resources.getString(mNameResId);
+            return new Channel(mId, name, mImportance, mGroupId);
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializer.java
index d7a9d36a..e88a699c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializer.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.notifications.channels;
 
+import android.content.res.Resources;
+
 import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
 
 /**
@@ -11,20 +13,20 @@
  */
 public class ChannelsInitializer {
     private final NotificationManagerProxy mNotificationManager;
-    private final ChannelDefinitions mChannelDefinitions;
+    private final Resources mResources;
 
-    public ChannelsInitializer(NotificationManagerProxy notificationManagerProxy,
-            ChannelDefinitions channelDefinitions) {
+    public ChannelsInitializer(
+            NotificationManagerProxy notificationManagerProxy, Resources resources) {
         mNotificationManager = notificationManagerProxy;
-        mChannelDefinitions = channelDefinitions;
+        mResources = resources;
     }
 
     /**
      * Creates all the channels on the notification manager that we want to appear in our
      * channel settings from first launch onwards.
      */
-    public void initializeStartupChannels() {
-        for (String channelId : mChannelDefinitions.getStartupChannelIds()) {
+    void initializeStartupChannels() {
+        for (String channelId : ChannelDefinitions.getStartupChannelIds()) {
             ensureInitialized(channelId);
         }
     }
@@ -34,7 +36,7 @@
      * It's safe to call this multiple times since deleting an already-deleted channel is a no-op.
      */
     void deleteLegacyChannels() {
-        for (String channelId : mChannelDefinitions.getLegacyChannelIds()) {
+        for (String channelId : ChannelDefinitions.getLegacyChannelIds()) {
             mNotificationManager.deleteNotificationChannel(channelId);
         }
     }
@@ -49,13 +51,14 @@
      * @param channelId The ID of the channel to be initialized.
      */
     public void ensureInitialized(@ChannelDefinitions.ChannelId String channelId) {
-        ChannelDefinitions.Channel channel = mChannelDefinitions.getChannelFromId(channelId);
-        if (channel == null) {
+        ChannelDefinitions.PredefinedChannel predefinedChannel =
+                ChannelDefinitions.getChannelFromId(channelId);
+        if (predefinedChannel == null) {
             throw new IllegalStateException("Could not initialize channel: " + channelId);
         }
         // Channel group must be created before the channel.
         mNotificationManager.createNotificationChannelGroup(
-                mChannelDefinitions.getChannelGroupFromId(channel));
-        mNotificationManager.createNotificationChannel(channel);
+                ChannelDefinitions.getChannelGroupFromId(predefinedChannel));
+        mNotificationManager.createNotificationChannel(predefinedChannel.toChannel(mResources));
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java
index cbec962..e8401430 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java
@@ -39,7 +39,7 @@
                                   new NotificationManagerProxyImpl(
                                           (NotificationManager) ContextUtils.getApplicationContext()
                                                   .getSystemService(Context.NOTIFICATION_SERVICE)),
-                                  new ChannelDefinitions()),
+                                  ContextUtils.getApplicationContext().getResources()),
                           ChannelDefinitions.CHANNELS_VERSION);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
index 5a5d768..5993449 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
@@ -17,8 +17,10 @@
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
+import android.util.TypedValue;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -80,6 +82,16 @@
     private static final String PARAM_CONDENSED_TILE_LAYOUT_FOR_LARGE_SCREENS_ENABLED =
             "condensed_tile_layout_for_large_screens_enabled";
 
+    /**
+     * Experiment parameter for whether to show the logo in the condensed layout.
+     */
+    private static final String PARAM_CONDENSED_LAYOUT_SHOW_LOGO = "condensed_layout_show_logo";
+
+    /**
+     * Experiment parameter for the logo height in dp in the condensed layout.
+     */
+    private static final String PARAM_CONDENSED_LAYOUT_LOGO_HEIGHT = "condensed_layout_logo_height";
+
     private NewTabPageRecyclerView mRecyclerView;
 
     private NewTabPageLayout mNewTabPageLayout;
@@ -244,7 +256,15 @@
 
         mSearchProviderLogoView =
                 (LogoView) mNewTabPageLayout.findViewById(R.id.search_provider_logo);
+        int experimentalLogoHeightDp = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                ChromeFeatureList.NTP_CONDENSED_LAYOUT, PARAM_CONDENSED_LAYOUT_LOGO_HEIGHT, 0);
+        if (experimentalLogoHeightDp > 0) {
+            ViewGroup.LayoutParams logoParams = mSearchProviderLogoView.getLayoutParams();
+            logoParams.height = dpToPx(experimentalLogoHeightDp);
+            mSearchProviderLogoView.setLayoutParams(logoParams);
+        }
         mLogoDelegate = new LogoDelegateImpl(tab, mSearchProviderLogoView);
+
         mSearchBoxView = mNewTabPageLayout.findViewById(R.id.search_box);
         mNoSearchLogoSpacer = mNewTabPageLayout.findViewById(R.id.no_search_logo_spacer);
 
@@ -559,11 +579,9 @@
     public void setSearchProviderHasLogo(boolean hasLogo) {
         if (hasLogo == mSearchProviderHasLogo && mInitialized) return;
         mSearchProviderHasLogo = hasLogo;
-        boolean showLogo = mSearchProviderHasLogo
-                && !ChromeFeatureList.isEnabled(ChromeFeatureList.NTP_CONDENSED_LAYOUT);
 
         // Set a bit more top padding on the tile grid if there is no logo.
-        int paddingTop = getResources().getDimensionPixelSize(showLogo
+        int paddingTop = getResources().getDimensionPixelSize(shouldShowLogo()
                         ? R.dimen.tile_grid_layout_padding_top
                         : R.dimen.tile_grid_layout_no_logo_padding_top);
         mTileGridLayout.setPadding(0, paddingTop, 0, mTileGridLayout.getPaddingBottom());
@@ -571,7 +589,7 @@
         // Hide or show the views above the tile grid as needed, including logo, search box, and
         // spacers.
         int visibility = mSearchProviderHasLogo ? View.VISIBLE : View.GONE;
-        int logoVisibility = showLogo ? View.VISIBLE : View.GONE;
+        int logoVisibility = shouldShowLogo() ? View.VISIBLE : View.GONE;
         int childCount = mNewTabPageLayout.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mNewTabPageLayout.getChildAt(i);
@@ -850,6 +868,14 @@
                 PARAM_CONDENSED_TILE_LAYOUT_FOR_LARGE_SCREENS_ENABLED, false);
     }
 
+    private boolean shouldShowLogo() {
+        boolean condensedLayoutEnabled =
+                ChromeFeatureList.isEnabled(ChromeFeatureList.NTP_CONDENSED_LAYOUT);
+        boolean showLogoInCondensedLayout = ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
+                ChromeFeatureList.NTP_CONDENSED_LAYOUT, PARAM_CONDENSED_LAYOUT_SHOW_LOGO, false);
+        return mSearchProviderHasLogo && (!condensedLayoutEnabled || showLogoInCondensedLayout);
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         if (mNewTabPageLayout != null) {
@@ -929,4 +955,12 @@
             updateSearchBoxOnScroll();
         }
     }
+
+    /**
+     * Converts a dp value to a px value.
+     */
+    private int dpToPx(int value) {
+        return Math.round(TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics()));
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java
index 8c09c2a..6b0c14a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java
@@ -11,6 +11,7 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.components.offlinepages.DeletePageResult;
 import org.chromium.content_public.browser.WebContents;
 
 import java.util.ArrayList;
@@ -191,6 +192,19 @@
     }
 
     /**
+     * Gets the offline pages associated with the provided namespace.
+     *
+     * @param namespace The string form of the namespace to query.
+     * @return A list of {@link OfflinePageItem} matching the provided namespace, or an empty list
+     * if none exist.
+     */
+    public void getPagesForNamespace(
+            final String namespace, final Callback<List<OfflinePageItem>> callback) {
+        List<OfflinePageItem> result = new ArrayList<>();
+        nativeGetPagesForNamespace(mNativeOfflinePageBridge, result, namespace, callback);
+    }
+
+    /**
      * Gets all the URLs in the request queue.
      *
      * @return A list of {@link SavePageRequest} representing all the queued requests.
@@ -381,6 +395,29 @@
     }
 
     /**
+     * Deletes offline pages based on the list of offline IDs. Calls the callback
+     * when operation is complete. Note that offline IDs are not intended to be saved across
+     * restarts of Chrome; they should be obtained by querying the model for the appropriate client
+     * ID.
+     *
+     * @param offlineIds A list of offline IDs of pages that will be deleted.
+     * @param callback A callback that will be called once operation is completed, called with the
+     *     DeletePageResult of the operation..
+     */
+    public void deletePagesByOfflineId(List<Long> offlineIdList, Callback<Integer> callback) {
+        if (offlineIdList == null) {
+            callback.onResult(Integer.valueOf(DeletePageResult.SUCCESS));
+            return;
+        }
+
+        long[] offlineIds = new long[offlineIdList.size()];
+        for (int i = 0; i < offlineIdList.size(); i++) {
+            offlineIds[i] = offlineIdList.get(i).longValue();
+        }
+        nativeDeletePagesByOfflineId(mNativeOfflinePageBridge, offlineIds, callback);
+    }
+
+    /**
      * Whether or not the underlying offline page model is loaded.
      */
     public boolean isOfflinePageModelLoaded() {
@@ -564,6 +601,7 @@
     private native void nativeRegisterRecentTab(long nativeOfflinePageBridge, int tabId);
     private native void nativeWillCloseTab(long nativeOfflinePageBridge, WebContents webContents);
     private native void nativeUnregisterRecentTab(long nativeOfflinePageBridge, int tabId);
+
     @VisibleForTesting
     native void nativeGetRequestsInQueue(
             long nativeOfflinePageBridge, Callback<SavePageRequest[]> callback);
@@ -576,9 +614,16 @@
     @VisibleForTesting
     native void nativeGetPagesByClientId(long nativeOfflinePageBridge, List<OfflinePageItem> result,
             String[] namespaces, String[] ids, Callback<List<OfflinePageItem>> callback);
+    native void nativeGetPagesForNamespace(long nativeOfflinePageBridge,
+            List<OfflinePageItem> result, String nameSpace,
+            Callback<List<OfflinePageItem>> callback);
     @VisibleForTesting
     native void nativeDeletePagesByClientId(long nativeOfflinePageBridge, String[] namespaces,
             String[] ids, Callback<Integer> callback);
+    @VisibleForTesting
+    native void nativeDeletePagesByOfflineId(
+            long nativeOfflinePageBridge, long[] offlineIds, Callback<Integer> callback);
+
     private native void nativeSelectPageForOnlineUrl(
             long nativeOfflinePageBridge, String onlineUrl, int tabId,
             Callback<OfflinePageItem> callback);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
index 3c9164b..a4a0dda8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
@@ -26,6 +26,7 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.inputmethod.EditorInfo;
 import android.widget.Button;
 import android.widget.CheckBox;
@@ -153,6 +154,13 @@
         mPhoneFormatter = new PhoneNumberUtil.FormatTextWatcher();
     }
 
+    /** Prevents screenshots of this editor. */
+    public void disableScreenshots() {
+        WindowManager.LayoutParams attributes = getWindow().getAttributes();
+        attributes.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+        getWindow().setAttributes(attributes);
+    }
+
     /** Launches the Autofill help page on top of the current Context. */
     public static void launchAutofillHelpPage(Context context) {
         CustomTabActivity.showInfoPage(context, HELP_URL);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
index f8272db..df99097 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
@@ -42,6 +42,7 @@
 import org.chromium.base.Callback;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeVersionInfo;
 import org.chromium.chrome.browser.payments.ShippingStrings;
 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.LineItemBreakdownSection;
 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.OptionSection;
@@ -437,6 +438,11 @@
         mEditorView = new EditorView(activity, sObserverForTest);
         mCardEditorView = new EditorView(activity, sObserverForTest);
 
+        // Allow screenshots of the credit card number in Canary, Dev, and developer builds.
+        if (ChromeVersionInfo.isBetaBuild() || ChromeVersionInfo.isStableBuild()) {
+            mCardEditorView.disableScreenshots();
+        }
+
         // Set up the dialog.
         mDialog = new AlwaysDismissedDialog(activity, R.style.DialogWhenLarge);
         mDialog.setOnDismissListener(this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill/AutofillLocalCardEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill/AutofillLocalCardEditor.java
index 5b61177..56454e9f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill/AutofillLocalCardEditor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill/AutofillLocalCardEditor.java
@@ -10,6 +10,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
@@ -17,6 +18,7 @@
 import android.widget.Spinner;
 
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeVersionInfo;
 import org.chromium.chrome.browser.autofill.PersonalDataManager;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
@@ -43,6 +45,13 @@
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
+        // Allow screenshots of the credit card number in Canary, Dev, and developer builds.
+        if (ChromeVersionInfo.isBetaBuild() || ChromeVersionInfo.isStableBuild()) {
+            WindowManager.LayoutParams attributes = getActivity().getWindow().getAttributes();
+            attributes.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+            getActivity().getWindow().setAttributes(attributes);
+        }
+
         View v = super.onCreateView(inflater, container, savedInstanceState);
 
         mNameLabel = (CompatibilityTextInputLayout) v.findViewById(R.id.credit_card_name_label);
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index eab27a4..8cdba075 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -209,6 +209,12 @@
       <message name="IDS_SEARCH" desc="The label for a search button.">
         Search
       </message>
+      <message name="IDS_SHOW_INFO" desc="The label for a info button to show info.">
+        Show Info
+      </message>
+      <message name="IDS_HIDE_INFO" desc="The label for a info button to hide info.">
+        Hide Info
+      </message>
       <message name="IDS_COPY_LINK" desc="The label for a menu item to copy a link. [CHAR-LIMIT=30]">
         Copy link
       </message>
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index c9c0e20a..99bb6f6 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -594,6 +594,7 @@
   "java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java",
   "java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilder.java",
   "java/src/org/chromium/chrome/browser/notifications/WebApkNotificationClient.java",
+  "java/src/org/chromium/chrome/browser/notifications/channels/Channel.java",
   "java/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitions.java",
   "java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializer.java",
   "java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java",
@@ -1457,6 +1458,7 @@
   "javatests/src/org/chromium/chrome/browser/notifications/NotificationTestUtil.java",
   "javatests/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java",
   "javatests/src/org/chromium/chrome/browser/notifications/NotificationBuilderBaseTest.java",
+  "javatests/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java",
   "javatests/src/org/chromium/chrome/browser/ntp/ChromeHomeNewTabPageTest.java",
   "javatests/src/org/chromium/chrome/browser/ntp/NewTabPageNavigationTest.java",
   "javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java",
@@ -1713,7 +1715,6 @@
   "junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java",
   "junit/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeUnitTest.java",
   "junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java",
-  "junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java",
   "junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java",
   "junit/src/org/chromium/chrome/browser/ntp/NativePageFactoryTest.java",
   "junit/src/org/chromium/chrome/browser/ntp/TitleUtilTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java
index cfaba43d3..dba4111b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java
@@ -34,8 +34,8 @@
 import org.chromium.chrome.test.util.ChromeRestriction;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.content.browser.BindingManager;
+import org.chromium.content.browser.ChildProcessConnection;
 import org.chromium.content.browser.ChildProcessLauncher;
-import org.chromium.content.browser.ManagedChildProcessConnection;
 import org.chromium.content.browser.test.ChildProcessAllocatorSettings;
 import org.chromium.content.browser.test.util.Criteria;
 import org.chromium.content.browser.test.util.CriteriaHelper;
@@ -113,7 +113,7 @@
         }
 
         @Override
-        public void addNewConnection(int pid, ManagedChildProcessConnection connection) {
+        public void addNewConnection(int pid, ChildProcessConnection connection) {
             synchronized (mVisibilityCallsMap) {
                 mVisibilityCallsMap.put(pid, "");
             }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
index 851c84dd..dd20e4d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.download;
 
 import android.content.Intent;
+import android.content.SharedPreferences.Editor;
 import android.os.Handler;
 import android.os.Looper;
 import android.support.test.filters.MediumTest;
@@ -15,6 +16,7 @@
 import android.widget.Spinner;
 import android.widget.TextView;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseActivityInstrumentationTestCase;
 import org.chromium.base.test.util.CallbackHelper;
@@ -108,6 +110,9 @@
         }
     }
 
+    private static final String PREF_SHOW_STORAGE_INFO_HEADER =
+            "download_home_show_storage_info_header";
+
     private StubbedProvider mStubbedProvider;
     private TestObserver mAdapterObserver;
     private DownloadManagerUi mUi;
@@ -124,6 +129,9 @@
     public void setUp() throws Exception {
         super.setUp();
 
+        Editor editor = ContextUtils.getAppSharedPreferences().edit();
+        editor.putBoolean(PREF_SHOW_STORAGE_INFO_HEADER, true).apply();
+
         mStubbedProvider = new StubbedProvider();
         DownloadManagerUi.setProviderForTests(mStubbedProvider);
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapterTest.java
index aba0daa..591a2a1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapterTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapterTest.java
@@ -8,6 +8,7 @@
 import static org.chromium.chrome.browser.widget.DateDividedAdapter.TYPE_HEADER;
 import static org.chromium.chrome.browser.widget.DateDividedAdapter.TYPE_NORMAL;
 
+import android.content.SharedPreferences.Editor;
 import android.support.test.filters.SmallTest;
 import android.support.v7.widget.RecyclerView;
 
@@ -17,6 +18,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.CallbackHelper;
@@ -70,6 +72,9 @@
      */
     private static final Integer HEADER = -1;
 
+    private static final String PREF_SHOW_STORAGE_INFO_HEADER =
+            "download_home_show_storage_info_header";
+
     private DownloadHistoryAdapter mAdapter;
     private Observer mObserver;
     private StubbedDownloadDelegate mDownloadDelegate;
@@ -82,6 +87,8 @@
         mBackendProvider = new StubbedProvider();
         mDownloadDelegate = mBackendProvider.getDownloadDelegate();
         mOfflineDelegate = mBackendProvider.getOfflinePageBridge();
+        Editor editor = ContextUtils.getAppSharedPreferences().edit();
+        editor.putBoolean(PREF_SHOW_STORAGE_INFO_HEADER, true).apply();
     }
 
     private void initializeAdapter(boolean showOffTheRecord) throws Exception {
@@ -158,6 +165,50 @@
         Assert.assertEquals(11, mAdapter.getTotalDownloadSize());
     }
 
+    /** Storage header shouldn't show up if user has already turned it off. */
+    @Test
+    @SmallTest
+    public void testInitialize_SingleItemNoStorageHeader() throws Exception {
+        Editor editor = ContextUtils.getAppSharedPreferences().edit();
+        editor.putBoolean(PREF_SHOW_STORAGE_INFO_HEADER, false).apply();
+        DownloadItem item = StubbedProvider.createDownloadItem(0, "19840116 12:00");
+        mDownloadDelegate.regularItems.add(item);
+        initializeAdapter(false);
+        checkAdapterContents(null, item);
+        Assert.assertEquals(1, mAdapter.getTotalDownloadSize());
+    }
+
+    /** Toggle the info button. Storage header should turn off/on accordingly. */
+    @Test
+    @SmallTest
+    public void testToggleStorageHeader() throws Exception {
+        DownloadItem item0 = StubbedProvider.createDownloadItem(0, "19840116 12:00");
+        DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840116 12:01");
+        mDownloadDelegate.regularItems.add(item0);
+        mDownloadDelegate.regularItems.add(item1);
+        initializeAdapter(false);
+        checkAdapterContents(HEADER, null, item1, item0);
+        Assert.assertEquals(11, mAdapter.getTotalDownloadSize());
+
+        // Turn off info and check that header is gone.
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                mAdapter.setShowStorageInfoHeader(false);
+            }
+        });
+        checkAdapterContents(null, item1, item0);
+
+        // Turn on info and check that header is back again.
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                mAdapter.setShowStorageInfoHeader(true);
+            }
+        });
+        checkAdapterContents(HEADER, null, item1, item0);
+    }
+
     /** Off the record downloads are ignored if the DownloadHistoryAdapter isn't watching them. */
     @Test
     @SmallTest
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java
new file mode 100644
index 0000000..db541ed
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java
@@ -0,0 +1,250 @@
+// 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.notifications.channels;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import android.annotation.TargetApi;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.BuildInfo;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
+import org.chromium.chrome.browser.notifications.NotificationManagerProxyImpl;
+import org.chromium.chrome.test.util.browser.notifications.MockNotificationManagerProxy;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Instrumentation tests for ChannelsInitializer.
+ *
+ * These are Android instrumentation tests so that resource strings can be accessed, and so that
+ * we can test against the real NotificationManager implementation.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class ChannelsInitializerTest {
+    private static final String MISCELLANEOUS_CHANNEL_ID = "miscellaneous";
+    private ChannelsInitializer mChannelsInitializer;
+    private NotificationManagerProxy mNotificationManagerProxy;
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mNotificationManagerProxy = new NotificationManagerProxyImpl(
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE));
+        mChannelsInitializer =
+                new ChannelsInitializer(mNotificationManagerProxy, mContext.getResources());
+        // Delete any channels that may already have been initialized. Cleaning up here rather than
+        // in tearDown in case tests running before these ones caused channels to be created.
+        for (String channelId : ChannelDefinitions.getStartupChannelIds()) {
+            mNotificationManagerProxy.deleteNotificationChannel(channelId);
+        }
+    }
+
+    @Test
+    @SmallTest
+    // TODO(crbug.com/685808) Replace this with VERSION_CODES.O & remove isAtLeastO check below.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    @Feature({"Browser", "Notifications"})
+    public void testDeleteLegacyChannels_noopOnCurrentDefinitions() throws Exception {
+        if (!BuildInfo.isAtLeastO()) return;
+        assertThat(getChannelsIgnoringMiscellaneous(), is(empty()));
+
+        mChannelsInitializer.deleteLegacyChannels();
+        assertThat(getChannelsIgnoringMiscellaneous(), is(empty()));
+
+        mChannelsInitializer.initializeStartupChannels();
+        assertThat(getChannelsIgnoringMiscellaneous(), is(not(empty())));
+
+        int nChannels = getChannelsIgnoringMiscellaneous().size();
+        mChannelsInitializer.deleteLegacyChannels();
+        assertThat(getChannelsIgnoringMiscellaneous(), hasSize(nChannels));
+    }
+
+    @Test
+    @SmallTest
+    // TODO(crbug.com/685808) Replace this with VERSION_CODES.O & remove isAtLeastO check below.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    @Feature({"Browser", "Notifications"})
+    public void testInitializeStartupChannels() throws Exception {
+        if (!BuildInfo.isAtLeastO()) return;
+        mChannelsInitializer.initializeStartupChannels();
+        List<String> notificationChannelIds = new ArrayList<>();
+        for (Channel channel : getChannelsIgnoringMiscellaneous()) {
+            notificationChannelIds.add(channel.getId());
+        }
+        assertThat(notificationChannelIds,
+                containsInAnyOrder(ChannelDefinitions.CHANNEL_ID_BROWSER,
+                        ChannelDefinitions.CHANNEL_ID_DOWNLOADS,
+                        ChannelDefinitions.CHANNEL_ID_INCOGNITO,
+                        ChannelDefinitions.CHANNEL_ID_SITES, ChannelDefinitions.CHANNEL_ID_MEDIA));
+    }
+
+    @Test
+    @SmallTest
+    // TODO(crbug.com/685808) Replace this with VERSION_CODES.O & remove isAtLeastO check below.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    @Feature({"Browser", "Notifications"})
+    public void testInitializeStartupChannels_groupCreated() throws Exception {
+        if (!BuildInfo.isAtLeastO()) return;
+        // Use a mock notification manager since the real one does not allow us to query which
+        // groups have been created.
+        MockNotificationManagerProxy mockNotificationManager = new MockNotificationManagerProxy();
+        ChannelsInitializer channelsInitializer =
+                new ChannelsInitializer(mockNotificationManager, mContext.getResources());
+        channelsInitializer.initializeStartupChannels();
+        assertThat(mockNotificationManager.getNotificationChannelGroups(), hasSize(1));
+        assertThat(mockNotificationManager.getNotificationChannelGroups().get(0).mId,
+                is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
+    }
+
+    @Test
+    @SmallTest
+    // TODO(crbug.com/685808) Replace this with VERSION_CODES.O & remove isAtLeastO check below.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    @Feature({"Browser", "Notifications"})
+    public void testEnsureInitialized_browserChannel() throws Exception {
+        if (!BuildInfo.isAtLeastO()) return;
+        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_BROWSER);
+
+        assertThat(getChannelsIgnoringMiscellaneous(), hasSize(1));
+        Channel channel = getChannelsIgnoringMiscellaneous().get(0);
+        assertThat(channel.getId(), is(ChannelDefinitions.CHANNEL_ID_BROWSER));
+        assertThat(channel.getName().toString(),
+                is(mContext.getString(org.chromium.chrome.R.string.notification_category_browser)));
+        assertThat(channel.getImportance(), is(NotificationManager.IMPORTANCE_LOW));
+        assertThat(channel.getGroupId(), is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
+    }
+
+    @Test
+    @SmallTest
+    // TODO(crbug.com/685808) Replace this with VERSION_CODES.O & remove isAtLeastO check below.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    @Feature({"Browser", "Notifications"})
+    public void testEnsureInitialized_downloadsChannel() throws Exception {
+        if (!BuildInfo.isAtLeastO()) return;
+        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_DOWNLOADS);
+
+        assertThat(getChannelsIgnoringMiscellaneous(), hasSize(1));
+        Channel channel = getChannelsIgnoringMiscellaneous().get(0);
+        assertThat(channel.getId(), is(ChannelDefinitions.CHANNEL_ID_DOWNLOADS));
+        assertThat(channel.getName().toString(),
+                is(mContext.getString(
+                        org.chromium.chrome.R.string.notification_category_downloads)));
+        assertThat(channel.getImportance(), is(NotificationManager.IMPORTANCE_LOW));
+        assertThat(channel.getGroupId(), is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
+    }
+
+    @Test
+    @SmallTest
+    // TODO(crbug.com/685808) Replace this with VERSION_CODES.O & remove isAtLeastO check below.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    @Feature({"Browser", "Notifications"})
+    public void testEnsureInitialized_incognitoChannel() throws Exception {
+        if (!BuildInfo.isAtLeastO()) return;
+        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_INCOGNITO);
+
+        assertThat(getChannelsIgnoringMiscellaneous(), hasSize(1));
+        Channel channel = getChannelsIgnoringMiscellaneous().get(0);
+        assertThat(channel.getId(), is(ChannelDefinitions.CHANNEL_ID_INCOGNITO));
+        assertThat(channel.getName().toString(),
+                is(mContext.getString(
+                        org.chromium.chrome.R.string.notification_category_incognito)));
+        assertThat(channel.getImportance(), is(NotificationManager.IMPORTANCE_LOW));
+        assertThat(channel.getGroupId(), is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
+    }
+
+    @Test
+    @SmallTest
+    // TODO(crbug.com/685808) Replace this with VERSION_CODES.O & remove isAtLeastO check below.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    @Feature({"Browser", "Notifications"})
+    public void testEnsureInitialized_mediaChannel() throws Exception {
+        if (!BuildInfo.isAtLeastO()) return;
+        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_MEDIA);
+
+        assertThat(getChannelsIgnoringMiscellaneous(), hasSize(1));
+        Channel channel = getChannelsIgnoringMiscellaneous().get(0);
+        assertThat(channel.getId(), is(ChannelDefinitions.CHANNEL_ID_MEDIA));
+        assertThat(channel.getName().toString(),
+                is(mContext.getString(org.chromium.chrome.R.string.notification_category_media)));
+        assertThat(channel.getImportance(), is(NotificationManager.IMPORTANCE_LOW));
+        assertThat(channel.getGroupId(), is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
+    }
+
+    @Test
+    @SmallTest
+    // TODO(crbug.com/685808) Replace this with VERSION_CODES.O & remove isAtLeastO check below.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    @Feature({"Browser", "Notifications"})
+    public void testEnsureInitialized_sitesChannel() throws Exception {
+        if (!BuildInfo.isAtLeastO()) return;
+        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_SITES);
+
+        assertThat(getChannelsIgnoringMiscellaneous(), hasSize(1));
+
+        Channel channel = getChannelsIgnoringMiscellaneous().get(0);
+        assertThat(channel.getId(), is(ChannelDefinitions.CHANNEL_ID_SITES));
+        assertThat(channel.getName().toString(),
+                is(mContext.getString(org.chromium.chrome.R.string.notification_category_sites)));
+        assertThat(channel.getImportance(), is(NotificationManager.IMPORTANCE_DEFAULT));
+        assertThat(channel.getGroupId(), is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
+    }
+
+    @Test
+    @SmallTest
+    // TODO(crbug.com/685808) Replace this with VERSION_CODES.O & remove isAtLeastO check below.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    @Feature({"Browser", "Notifications"})
+    public void testEnsureInitialized_multipleCalls() throws Exception {
+        if (!BuildInfo.isAtLeastO()) return;
+        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_SITES);
+        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_BROWSER);
+        assertThat(getChannelsIgnoringMiscellaneous(), hasSize(2));
+    }
+
+    /**
+     * Gets the current notification channels from the notification manager, except for any with
+     * the id 'miscellaneous', which will be removed from the list before returning.
+     *
+     * (Android *might* add a Miscellaneous channel on our behalf, but we don't want to tie our
+     * tests to its presence, as this could change).
+     */
+    private List<Channel> getChannelsIgnoringMiscellaneous() {
+        List<Channel> channels = mNotificationManagerProxy.getNotificationChannels();
+        for (Iterator<Channel> it = channels.iterator(); it.hasNext();) {
+            Channel channel = it.next();
+            if (channel.getId().equals(MISCELLANEOUS_CHANNEL_ID)) it.remove();
+        }
+        return channels;
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeTest.java
index c6a6038..a45433d7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeTest.java
@@ -37,6 +37,7 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
 /** Unit tests for {@link OfflinePageBridge}. */
@@ -283,9 +284,93 @@
         Assert.assertEquals(requests[0].getUrl(), remaining[0].getUrl());
     }
 
-    private void savePage(final int expectedResult, final String expectedUrl)
+    @Test
+    @SmallTest
+    public void testDeletePagesByOfflineIds() throws Exception {
+        // Save 3 pages and record their offline IDs to delete later.
+        Set<String> pageUrls = new HashSet<>();
+        pageUrls.add(mTestPage);
+        pageUrls.add(mTestPage + "?foo=1");
+        pageUrls.add(mTestPage + "?foo=2");
+        int pagesToDeleteCount = pageUrls.size();
+        List<Long> offlineIdsToDelete = new ArrayList<>();
+        for (String url : pageUrls) {
+            mActivityTestRule.loadUrl(url);
+            offlineIdsToDelete.add(savePage(SavePageResult.SUCCESS, url));
+        }
+        Assert.assertEquals("The pages should exist now that we saved them.", pagesToDeleteCount,
+                checkPagesExistOffline(pageUrls).size());
+
+        // Save one more page but don't save the offline ID, this page should not be deleted.
+        Set<String> pageUrlsToSave = new HashSet<>();
+        String pageToSave = mTestPage + "?bar=1";
+        pageUrlsToSave.add(pageToSave);
+        int pagesToSaveCount = pageUrlsToSave.size();
+        for (String url : pageUrlsToSave) {
+            mActivityTestRule.loadUrl(url);
+            savePage(SavePageResult.SUCCESS, pageToSave);
+        }
+        Assert.assertEquals("The pages should exist now that we saved them.", pagesToSaveCount,
+                checkPagesExistOffline(pageUrlsToSave).size());
+
+        // Delete the first 3 pages.
+        deletePages(offlineIdsToDelete);
+        Assert.assertEquals(
+                "The page should cease to exist.", 0, checkPagesExistOffline(pageUrls).size());
+
+        // We should not have deleted the one we didn't ask to delete.
+        Assert.assertEquals("The page should not be deleted.", pagesToSaveCount,
+                checkPagesExistOffline(pageUrlsToSave).size());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetPagesForNamespace() throws Exception {
+        // Save 3 pages and record their offline IDs to delete later.
+        Set<Long> offlineIdsToFetch = new HashSet<>();
+        for (int i = 0; i < 3; i++) {
+            String url = mTestPage + "?foo=" + i;
+            mActivityTestRule.loadUrl(url);
+            offlineIdsToFetch.add(savePage(SavePageResult.SUCCESS, url));
+        }
+
+        // Save a page in a different namespace.
+        String urlToIgnore = mTestPage + "?bar=1";
+        mActivityTestRule.loadUrl(urlToIgnore);
+        long offlineIdToIgnore = savePage(SavePageResult.SUCCESS, urlToIgnore,
+                new ClientId(OfflinePageBridge.ASYNC_NAMESPACE, "-42"));
+
+        List<OfflinePageItem> pages = getPagesForNamespace(OfflinePageBridge.BOOKMARK_NAMESPACE);
+        Assert.assertEquals(
+                "The number of pages returned does not match the number of pages saved.",
+                offlineIdsToFetch.size(), pages.size());
+        for (OfflinePageItem page : pages) {
+            offlineIdsToFetch.remove(page.getOfflineId());
+        }
+        Assert.assertEquals(
+                "There were different pages saved than those returned by getPagesForNamespace.", 0,
+                offlineIdsToFetch.size());
+
+        // Check that the page in the other namespace still exists.
+        List<OfflinePageItem> asyncPages = getPagesForNamespace(OfflinePageBridge.ASYNC_NAMESPACE);
+        Assert.assertEquals("The page saved in an alternate namespace is no longer there.", 1,
+                asyncPages.size());
+        Assert.assertEquals(
+                "The offline ID of the page saved in an alternate namespace does not match.",
+                offlineIdToIgnore, asyncPages.get(0).getOfflineId());
+    }
+
+    // Returns offline ID.
+    private long savePage(final int expectedResult, final String expectedUrl)
             throws InterruptedException {
+        return savePage(expectedResult, expectedUrl, BOOKMARK_ID);
+    }
+
+    // Returns offline ID.
+    private long savePage(final int expectedResult, final String expectedUrl,
+            final ClientId clientId) throws InterruptedException {
         final Semaphore semaphore = new Semaphore(0);
+        final AtomicLong result = new AtomicLong(-1);
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
@@ -297,8 +382,8 @@
                         mActivityTestRule.getActivity().getActivityTab().getWebContents());
 
                 mOfflinePageBridge.savePage(
-                        mActivityTestRule.getActivity().getActivityTab().getWebContents(),
-                        BOOKMARK_ID, new SavePageCallback() {
+                        mActivityTestRule.getActivity().getActivityTab().getWebContents(), clientId,
+                        new SavePageCallback() {
                             @Override
                             public void onSavePageDone(
                                     int savePageResult, String url, long offlineId) {
@@ -306,12 +391,30 @@
                                         "Requested and returned URLs differ.", expectedUrl, url);
                                 Assert.assertEquals(
                                         "Save result incorrect.", expectedResult, savePageResult);
+                                result.set(offlineId);
                                 semaphore.release();
                             }
                         });
             }
         });
         Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        return result.get();
+    }
+
+    private void deletePages(final List<Long> offlineIds) throws InterruptedException {
+        final Semaphore semaphore = new Semaphore(0);
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mOfflinePageBridge.deletePagesByOfflineId(offlineIds, new Callback<Integer>() {
+                    @Override
+                    public void onResult(Integer deletePageResult) {
+                        semaphore.release();
+                    }
+                });
+            }
+        });
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     private void deletePage(final ClientId bookmarkId, final int expectedResult)
@@ -353,6 +456,27 @@
         return result;
     }
 
+    private List<OfflinePageItem> getPagesForNamespace(final String namespace)
+            throws InterruptedException {
+        final List<OfflinePageItem> result = new ArrayList<OfflinePageItem>();
+        final Semaphore semaphore = new Semaphore(0);
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mOfflinePageBridge.getPagesForNamespace(
+                        namespace, new Callback<List<OfflinePageItem>>() {
+                            @Override
+                            public void onResult(List<OfflinePageItem> pages) {
+                                result.addAll(pages);
+                                semaphore.release();
+                            }
+                        });
+            }
+        });
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        return result;
+    }
+
     private void forceConnectivityStateOnUiThread(final boolean state) {
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestAbortTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestAbortTest.java
index 33fee62..13ebc38f8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestAbortTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestAbortTest.java
@@ -6,11 +6,20 @@
 
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,11 +27,15 @@
 /**
  * A payment integration test for a merchant that aborts their payment request.
  */
-public class PaymentRequestAbortTest extends PaymentRequestTestBase {
-    public PaymentRequestAbortTest() {
-        // This merchant aborts the payment request when the "abort" button is clicked.
-        super("payment_request_abort_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestAbortTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_abort_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -37,23 +50,27 @@
     }
 
     /** If the user has not clicked "Pay" yet, then merchant's abort will succeed. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testAbortBeforePayClicked() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickNodeAndWait("abort", getDismissed());
-        expectResultContains(new String[] {"Aborted"});
+    public void testAbortBeforePayClicked()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickNodeAndWait("abort", mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Aborted"});
     }
 
     /** If the user has already clicked the "Pay" button, then merchant won't be able to abort. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testAbortWhileUnmaskingCard() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        clickNodeAndWait("abort", getUnableToAbort());
-        expectResultContains(new String[] {"Cannot abort"});
+    public void testAbortWhileUnmaskingCard()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.clickNodeAndWait(
+                "abort", mPaymentRequestTestRule.getUnableToAbort());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Cannot abort"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressTest.java
index 70db97b9..d5e3226 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressTest.java
@@ -4,13 +4,27 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DECEMBER;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.FIRST_BILLING_ADDRESS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NEXT_YEAR;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,8 +32,17 @@
 /**
  * A payment integration test for biling addresses.
  */
-public class PaymentRequestBillingAddressTest extends PaymentRequestTestBase {
-    /*
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestBillingAddressTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_free_shipping_test.html", this);
+
+    /**
      * The index at which the option to add a billing address is located in the billing address
      * selection dropdown.
      */
@@ -28,10 +51,6 @@
     /** The index of the billing address dropdown in the card editor. */
     private static final int BILLING_ADDRESS_DROPDOWN_INDEX = 2;
 
-    public PaymentRequestBillingAddressTest() {
-        super("payment_request_free_shipping_test.html");
-    }
-
     @Override
     public void onMainActivityStarted()
             throws InterruptedException, ExecutionException, TimeoutException {
@@ -92,287 +111,395 @@
     }
 
     /** Verifies the format of the billing address suggestions when adding a new credit card. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNewCardBillingAddressFormat()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInCardEditorAndWait(
-                new String[] {"5454-5454-5454-5454", "Bob"}, getEditorTextUpdate());
-        setSpinnerSelectionsInCardEditorAndWait(
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"5454-5454-5454-5454", "Bob"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
                 new int[] {DECEMBER, NEXT_YEAR, FIRST_BILLING_ADDRESS},
-                getBillingAddressChangeProcessed());
+                mPaymentRequestTestRule.getBillingAddressChangeProcessed());
         // The billing address suggestions should include only the name, address, city, state and
         // zip code of the profile.
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                .equals("Rob Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Rob Doe, 340 Main St, Los Angeles, CA 90291"));
     }
 
     /**
      * Verifies that the correct number of billing address suggestions are shown when adding a new
      * credit card.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNumberOfBillingAddressSuggestions()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // There should only be 8 suggestions, the 7 saved addresses and the option to add a new
         // address.
-        assertEquals(8, getSpinnerItemCountInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX));
+        Assert.assertEquals(8,
+                mPaymentRequestTestRule.getSpinnerItemCountInCardEditor(
+                        BILLING_ADDRESS_DROPDOWN_INDEX));
     }
 
     /**
      * Verifies that the correct number of billing address suggestions are shown when adding a new
      * credit card, even after cancelling out of adding a new billing address.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNumberOfBillingAddressSuggestions_AfterCancellingNewBillingAddress()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Add a payment method and add a new billing address.
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Select the "+ ADD ADDRESS" option for the billing address.
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS}, getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS},
+                mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the creation of a new billing address.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // There should still only be 8 suggestions, the 7 saved addresses and the option to add a
         // new address.
-        assertEquals(8, getSpinnerItemCountInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX));
+        Assert.assertEquals(8,
+                mPaymentRequestTestRule.getSpinnerItemCountInCardEditor(
+                        BILLING_ADDRESS_DROPDOWN_INDEX));
     }
 
     /**
      * Tests that for a card that already has a billing address, adding a new one and cancelling
      * maintains the previous selection. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddBillingAddressOnCardAndCancel_MaintainsPreviousSelection()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
         // Edit the only card.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_open_editor_pencil_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_open_editor_pencil_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Jon Doe is selected as the billing address.
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
 
         // Select the "+ ADD ADDRESS" option for the billing address.
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS}, getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS},
+                mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the creation of a new billing address.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Jon Doe is STILL selected as the billing address.
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
     }
 
     /**
      * Tests that adding a billing address for a card that has none, and cancelling then returns
      * to the proper selection (Select...).
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddBillingAddressOnCardWithNoBillingAndCancel_MaintainsPreviousSelection()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
         // Edit the second card.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickOnPaymentMethodSuggestionOptionAndWait(1, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickOnPaymentMethodSuggestionOptionAndWait(
+                1, mPaymentRequestTestRule.getReadyToEdit());
 
         // Now in Card Editor to add a billing address. "Select" is selected in the dropdown.
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                .equals("Select"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Select"));
 
         // Select the "+ ADD ADDRESS" option for the billing address.
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS}, getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS},
+                mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the creation of a new billing address.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // "Select" is STILL selected as the billing address.
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                .equals("Select"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Select"));
     }
 
     /**
      * Verifies that the billing address suggestions are ordered by frecency.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testBillingAddressSortedByFrecency()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Add a payment method.
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // There should be 8 suggestions, the 7 saved addresses and the option to add a new address.
-        assertEquals(8, getSpinnerItemCountInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX));
+        Assert.assertEquals(8,
+                mPaymentRequestTestRule.getSpinnerItemCountInCardEditor(
+                        BILLING_ADDRESS_DROPDOWN_INDEX));
 
         // The billing address suggestions should be ordered by frecency.
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                0).equals("Rob Doe, 340 Main St, Los Angeles, CA 90291"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                1).equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                2).equals("Tom Doe, 340 Main St, Los Angeles, CA 90291"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                7).equals("Add address"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 0)
+                        .equals("Rob Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 1)
+                        .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 2)
+                        .equals("Tom Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 7)
+                        .equals("Add address"));
     }
 
     /**
      * Verifies that the billing address suggestions are ordered by frecency, except for a newly
      * created address which should be suggested first.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testBillingAddressSortedByFrecency_AddNewAddress()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Add a payment method.
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Add a new billing address.
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS}, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {
-                "Seb Doe", "Google", "340 Main St", "Los Angeles", "CA", "90291",
-                "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS},
+                mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Seb Doe", "Google", "340 Main St", "Los Angeles", "CA", "90291",
+                        "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // There should be 9 suggestions, the 7 initial addresses, the newly added address and the
         // option to add a new address.
-        assertEquals(9, getSpinnerItemCountInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX));
+        Assert.assertEquals(9,
+                mPaymentRequestTestRule.getSpinnerItemCountInCardEditor(
+                        BILLING_ADDRESS_DROPDOWN_INDEX));
 
         // The fist suggestion should be the newly added address.
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                0).equals("Seb Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 0)
+                        .equals("Seb Doe, 340 Main St, Los Angeles, CA 90291"));
 
         // The rest of the billing address suggestions should be ordered by frecency.
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                1).equals("Rob Doe, 340 Main St, Los Angeles, CA 90291"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                2).equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                3).equals("Tom Doe, 340 Main St, Los Angeles, CA 90291"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                8).equals("Add address"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 1)
+                        .equals("Rob Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 2)
+                        .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 3)
+                        .equals("Tom Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 8)
+                        .equals("Add address"));
     }
 
     /**
      * Verifies that a newly created shipping address is offered as the first billing address
      * suggestion.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNewShippingAddressSuggestedFirst()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Add a shipping address.
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Seb Doe", "Google", "340 Main St", "Los Angeles",
-                "CA", "90291", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Seb Doe", "Google", "340 Main St", "Los Angeles", "CA", "90291",
+                        "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Navigate to the card editor UI.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // There should be 9 suggestions, the 7 initial addresses, the newly added address and the
         // option to add a new address.
-        assertEquals(9, getSpinnerItemCountInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX));
+        Assert.assertEquals(9,
+                mPaymentRequestTestRule.getSpinnerItemCountInCardEditor(
+                        BILLING_ADDRESS_DROPDOWN_INDEX));
 
         // The new address should be suggested first.
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                0).equals("Seb Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 0)
+                        .equals("Seb Doe, 340 Main St, Los Angeles, CA 90291"));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSelectIncompleteBillingAddress_EditComplete()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
         // Edit the second card.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickOnPaymentMethodSuggestionOptionAndWait(1, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickOnPaymentMethodSuggestionOptionAndWait(
+                1, mPaymentRequestTestRule.getReadyToEdit());
 
         // Now "Select" is selected in the dropdown.
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                .equals("Select"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Select"));
 
         // The incomplete addresses in the dropdown contain edit required messages.
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 4)
-                           .endsWith("Name required"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                5).endsWith("More information required"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 6)
-                           .endsWith("Enter a valid address"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 4)
+                        .endsWith("Name required"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 5)
+                        .endsWith("More information required"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 6)
+                        .endsWith("Enter a valid address"));
 
         // Selects the fourth billing addresss that misses recipient name brings up the address
         // editor.
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {DECEMBER, NEXT_YEAR, 4}, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Lisa Doh", "Google", "340 Main St", "Los Angeles",
-                "CA", "90291", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {DECEMBER, NEXT_YEAR, 4}, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Lisa Doh", "Google", "340 Main St", "Los Angeles", "CA", "90291",
+                        "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // The newly completed address must be selected and put at the top of the dropdown.
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                           .equals("Lisa Doh, 340 Main St, Los Angeles, CA 90291"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                0).equals("Lisa Doh, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Lisa Doh, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 0)
+                        .equals("Lisa Doh, 340 Main St, Los Angeles, CA 90291"));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSelectIncompleteBillingAddress_EditCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
         // Edit the only complete card.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_open_editor_pencil_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_open_editor_pencil_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Jon Doe is selected as the billing address.
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
 
         // The incomplete addresses in the dropdown contain edit required messages.
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 4)
-                           .endsWith("Name required"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                5).endsWith("More information required"));
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 6)
-                           .endsWith("Enter a valid address"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 4)
+                        .endsWith("Name required"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 5)
+                        .endsWith("More information required"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 6)
+                        .endsWith("Enter a valid address"));
 
         // Selects the fifth billing addresss that misses recipient name brings up the address
         // editor.
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {DECEMBER, NEXT_YEAR, 4}, getReadyToEdit());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {DECEMBER, NEXT_YEAR, 4}, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // The previous selected address should be selected after canceling out from edit.
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Jon Doe, 340 Main St, Los Angeles, CA 90291"));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressWithoutPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressWithoutPhoneTest.java
index 72e0543..1352ed3c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressWithoutPhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressWithoutPhoneTest.java
@@ -4,14 +4,27 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DECEMBER;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NEXT_YEAR;
+
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,7 +32,17 @@
 /**
  * A payment integration test for biling addresses without a phone.
  */
-public class PaymentRequestBillingAddressWithoutPhoneTest extends PaymentRequestTestBase {
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestBillingAddressWithoutPhoneTest implements MainActivityStartCallback {
+
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_free_shipping_test.html", this);
+
     /*
      * The index at which the option to add a billing address is located in the billing address
      * selection dropdown.
@@ -29,10 +52,6 @@
     /** The index of the billing address dropdown in the card editor. */
     private static final int BILLING_ADDRESS_DROPDOWN_INDEX = 2;
 
-    public PaymentRequestBillingAddressWithoutPhoneTest() {
-        super("payment_request_free_shipping_test.html");
-    }
-
     @Override
     public void onMainActivityStarted()
             throws InterruptedException, ExecutionException, TimeoutException {
@@ -52,71 +71,93 @@
         helper.setProfileUseStatsForTesting(address_with_phone, 5, 5);
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCanPayWithBillingNoPhone()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon NoPhone"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jon NoPhone"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCanSelectBillingAddressWithoutPhone()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Go edit the credit card.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickOnPaymentMethodSuggestionEditIconAndWait(0, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickOnPaymentMethodSuggestionEditIconAndWait(
+                0, mPaymentRequestTestRule.getReadyToEdit());
 
         // Make sure that the currently selected address is valid and can be selected (does not
         // include error messages).
-        assertTrue(getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
-                           .equals("Jon NoPhone, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerSelectionTextInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX)
+                        .equals("Jon NoPhone, 340 Main St, Los Angeles, CA 90291"));
 
         // Even though the current billing address is valid, the one with a phone number should be
         // suggested first if the user wants to change it.
-        assertTrue(getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX,
-                0).equals("Rob Phone, 340 Main St, Los Angeles, CA 90291"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule
+                        .getSpinnerTextAtPositionInCardEditor(BILLING_ADDRESS_DROPDOWN_INDEX, 0)
+                        .equals("Rob Phone, 340 Main St, Los Angeles, CA 90291"));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCantSelectShippingAddressWithoutPhone()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // The first suggestion should be the address with a phone.
-        assertTrue(getShippingAddressSuggestionLabel(0).contains("Rob Phone"));
-        assertFalse(getShippingAddressSuggestionLabel(0).endsWith("Phone number required"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule.getShippingAddressSuggestionLabel(0).contains("Rob Phone"));
+        Assert.assertFalse(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(0).endsWith(
+                "Phone number required"));
 
         // The address without a phone should be suggested after with a message indicating that the
         // phone number is required.
-        assertTrue(getShippingAddressSuggestionLabel(1).contains("Jon NoPhone"));
-        assertTrue(getShippingAddressSuggestionLabel(1).endsWith("Phone number required"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(1).contains(
+                "Jon NoPhone"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(1).endsWith(
+                "Phone number required"));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCantAddNewBillingAddressWithoutPhone()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Add a new billing address without a phone.
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS}, getReadyToEdit());
-        setTextInEditorAndWait(
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {DECEMBER, NEXT_YEAR, ADD_BILLING_ADDRESS},
+                mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
                 new String[] {"Seb Doe", "Google", "340 Main St", "Los Angeles", "CA", "90291", ""},
-                getEditorTextUpdate());
+                mPaymentRequestTestRule.getEditorTextUpdate());
 
         // Trying to add the address without a phone number should fail.
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBlobUrlTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBlobUrlTest.java
index 844d51f..3d4c030 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBlobUrlTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBlobUrlTest.java
@@ -6,27 +6,38 @@
 
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 
 /** Web payments test for blob URL.  */
-public class PaymentRequestBlobUrlTest extends PaymentRequestTestBase {
-    public PaymentRequestBlobUrlTest() {
-        super("payment_request_blob_url_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestBlobUrlTest {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_blob_url_test.html");
 
-    @Override
-    public void onMainActivityStarted()
-            throws InterruptedException, ExecutionException, TimeoutException {}
-
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void test() throws InterruptedException, ExecutionException, TimeoutException {
-        openPageAndClickNode("buy");
-        assertWaitForPageScaleFactorMatch(2);
-        expectResultContains(new String[] {"SecurityError: Failed to construct 'PaymentRequest': "
-                + "Must be in a secure context"});
+        mPaymentRequestTestRule.openPageAndClickNode("buy");
+        mPaymentRequestTestRule.assertWaitForPageScaleFactorMatch(2);
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"SecurityError: Failed to construct 'PaymentRequest': "
+                        + "Must be in a secure context"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentMetricsTest.java
index 053815c..08487930 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentMetricsTest.java
@@ -4,15 +4,28 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -20,10 +33,15 @@
 /**
  * A payment integration test for the correct log of the CanMakePayment metrics.
  */
-public class PaymentRequestCanMakePaymentMetricsTest extends PaymentRequestTestBase {
-    public PaymentRequestCanMakePaymentMetricsTest() {
-        super("payment_request_can_make_payment_metrics_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestCanMakePaymentMetricsTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_can_make_payment_metrics_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -39,39 +57,41 @@
      * calling it, receiving no as a response, still showing the Payment Request and the user aborts
      * the flow.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCannotMakePayment_Abort()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Initiate a payment request.
-        triggerUIAndWait("queryShow", getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "queryShow", mPaymentRequestTestRule.getReadyForInput());
 
         // Press the back button.
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI().getDialogForTest().onBackPressed();
+                mPaymentRequestTestRule.getPaymentRequestUI().getDialogForTest().onBackPressed();
             }
         });
-        getDismissed().waitForCallback(callCount);
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
 
         // CanMakePayment was queried.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Usage",
                         JourneyLogger.CAN_MAKE_PAYMENT_USED));
 
         // The CanMakePayment effect on show should be recorded as being false and shown.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Used.EffectOnShow",
                         JourneyLogger.CMP_SHOW_DID_SHOW));
 
         // There should be a record for an abort when CanMakePayment is false but the PR is shown to
         // the user.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Used.FalseWithShowEffectOnCompletion",
                         JourneyLogger.COMPLETION_STATUS_USER_ABORTED));
@@ -82,40 +102,48 @@
      * calling it, receiving no as a response, still showing the Payment Request and the user
      * completes the flow.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCannotMakePayment_Complete()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait("queryShow", getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "queryShow", mPaymentRequestTestRule.getReadyForInput());
 
         // Add a new credit card.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyToEdit());
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {11, 1, 0}, getBillingAddressChangeProcessed());
-        setTextInCardEditorAndWait(
-                new String[] {"4111111111111111", "Jon Doe"}, getEditorTextUpdate());
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {11, 1, 0}, mPaymentRequestTestRule.getBillingAddressChangeProcessed());
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"4111111111111111", "Jon Doe"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Complete the transaction.
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // CanMakePayment was queried.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Usage",
                         JourneyLogger.CAN_MAKE_PAYMENT_USED));
 
         // The CanMakePayment effect on show should be recorded as being false and shown.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Used.EffectOnShow",
                         JourneyLogger.CMP_SHOW_DID_SHOW));
 
         // There should be a record for a completion when CanMakePayment is false but the PR is
         // shown to the user.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Used.FalseWithShowEffectOnCompletion",
                         JourneyLogger.COMPLETION_STATUS_COMPLETED));
@@ -126,35 +154,37 @@
      * calling it, receiving yeas as a response, showing the Payment Request and the user aborts the
      * flow.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCanMakePayment_Abort()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Install the app so CanMakePayment returns true.
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
 
         // Initiate a payment request.
-        triggerUIAndWait("queryShow", getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "queryShow", mPaymentRequestTestRule.getReadyForInput());
 
         // Press the back button.
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI().getDialogForTest().onBackPressed();
+                mPaymentRequestTestRule.getPaymentRequestUI().getDialogForTest().onBackPressed();
             }
         });
-        getDismissed().waitForCallback(callCount);
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
 
         // CanMakePayment was queried.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Usage",
                         JourneyLogger.CAN_MAKE_PAYMENT_USED));
 
         // The CanMakePayment effect on show should be recorded as being false and shown.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Used.EffectOnShow",
                         JourneyLogger.CMP_SHOW_DID_SHOW
@@ -162,7 +192,7 @@
 
         // There should be a record for an abort when CanMakePayment is false but the PR is shown to
         // the user.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Used.TrueWithShowEffectOnCompletion",
                         JourneyLogger.COMPLETION_STATUS_USER_ABORTED));
@@ -173,25 +203,28 @@
      * calling it, receiving yeas as a response, showing the Payment Request and the user completes
      * the flow.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCanMakePayment_Complete()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Install the app so CanMakePayment returns true.
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
 
         // Initiate an complete a payment request.
-        triggerUIAndWait("queryShow", getReadyForInput());
-        clickAndWait(R.id.button_primary, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "queryShow", mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
 
         // CanMakePayment was queried.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Usage",
                         JourneyLogger.CAN_MAKE_PAYMENT_USED));
 
         // The CanMakePayment effect on show should be recorded as being false and shown.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Used.EffectOnShow",
                         JourneyLogger.CMP_SHOW_DID_SHOW
@@ -199,7 +232,7 @@
 
         // There should be a record for an abort when CanMakePayment is false but the PR is shown to
         // the user.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Used.TrueWithShowEffectOnCompletion",
                         JourneyLogger.COMPLETION_STATUS_COMPLETED));
@@ -209,33 +242,35 @@
      * Tests that the CanMakePayment metrics are correctly logged for the case of a merchant
      * not calling it but still showing the Payment Request and the user aborts the flow.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNoQuery_Abort()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Initiate a payment request.
-        triggerUIAndWait("noQueryShow", getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "noQueryShow", mPaymentRequestTestRule.getReadyForInput());
 
         // Press the back button.
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI().getDialogForTest().onBackPressed();
+                mPaymentRequestTestRule.getPaymentRequestUI().getDialogForTest().onBackPressed();
             }
         });
-        getDismissed().waitForCallback(callCount);
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
 
         // CanMakePayment was not queried.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Usage",
                         JourneyLogger.CAN_MAKE_PAYMENT_NOT_USED));
 
         // There should be a record for an abort when CanMakePayment is not called but the PR is
         // shown to the user.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.NotUsed.WithShowEffectOnCompletion",
                         JourneyLogger.COMPLETION_STATUS_USER_ABORTED));
@@ -245,26 +280,29 @@
      * Tests that the CanMakePayment metrics are correctly logged for the case of a merchant
      * not calling it but still showing the Payment Request and the user completes the flow.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNoQuery_Completes()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Install the app so the user can complete the Payment Request.
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
 
         // Initiate a payment request.
-        triggerUIAndWait("noQueryShow", getReadyForInput());
-        clickAndWait(R.id.button_primary, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "noQueryShow", mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
 
         // CanMakePayment was not queried.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.Usage",
                         JourneyLogger.CAN_MAKE_PAYMENT_NOT_USED));
 
         // There should be a record for a completion when CanMakePayment is not called but the PR is
         // shown to the user.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CanMakePayment.NotUsed.WithShowEffectOnCompletion",
                         JourneyLogger.COMPLETION_STATUS_COMPLETED));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java
index 13b70ce..3b3b6c7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java
@@ -4,12 +4,26 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NO_INSTRUMENTS;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,10 +32,15 @@
  * A payment integration test for checking whether user can make a payment via either payment
  * app or a credit card. This user does not have a complete credit card on file.
  */
-public class PaymentRequestCanMakePaymentQueryNoCardTest extends PaymentRequestTestBase {
-    public PaymentRequestCanMakePaymentQueryNoCardTest() {
-        super("payment_request_can_make_payment_query_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestCanMakePaymentQueryNoCardTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_can_make_payment_query_test.html", this);
 
     @Override
     public void onMainActivityStarted() throws InterruptedException, ExecutionException,
@@ -33,47 +52,57 @@
                 R.drawable.pr_visa, "" /* billingAddressId */, "" /* serverId */));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoBobPayInstalled() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"false"});
+    public void testNoBobPayInstalled()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"false"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoInstrumentsInFastBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(NO_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"false"});
+    public void testNoInstrumentsInFastBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(NO_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"false"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoInstrumentsInSlowBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(NO_INSTRUMENTS, DELAYED_RESPONSE);
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"false"});
+    public void testNoInstrumentsInSlowBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(NO_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"false"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testPayViaFastBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"true"});
+    public void testPayViaFastBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"true"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testPayViaSlowBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"true"});
+    public void testPayViaSlowBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"true"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryTest.java
index 20dc0a4..c5e22816 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryTest.java
@@ -4,12 +4,26 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NO_INSTRUMENTS;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,10 +32,15 @@
  * A payment integration test for checking whether user can make a payment via either payment app or
  * a credit card. This user has a valid credit card without a billing address on file.
  */
-public class PaymentRequestCanMakePaymentQueryTest extends PaymentRequestTestBase {
-    public PaymentRequestCanMakePaymentQueryTest() {
-        super("payment_request_can_make_payment_query_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestCanMakePaymentQueryTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_can_make_payment_query_test.html", this);
 
     @Override
     public void onMainActivityStarted() throws InterruptedException, ExecutionException,
@@ -33,47 +52,57 @@
                 "" /* billingAddressId */, "" /* serverId */));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoBobPayInstalled() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"true"});
+    public void testNoBobPayInstalled()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"true"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoInstrumentsInFastBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(NO_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"true"});
+    public void testNoInstrumentsInFastBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(NO_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"true"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoInstrumentsInSlowBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(NO_INSTRUMENTS, DELAYED_RESPONSE);
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"true"});
+    public void testNoInstrumentsInSlowBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(NO_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"true"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testPayViaFastBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"true"});
+    public void testPayViaFastBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"true"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testPayViaSlowBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"true"});
+    public void testPayViaSlowBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"true"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCardEditorAutoAdvanceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCardEditorAutoAdvanceTest.java
index 8a28d2a5..d8b71c3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCardEditorAutoAdvanceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCardEditorAutoAdvanceTest.java
@@ -7,12 +7,22 @@
 import android.support.test.filters.MediumTest;
 import android.view.View;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -23,10 +33,15 @@
  * Below valid test card numbers are from https://stripe.com/docs/testing#cards and
  * https://developers.braintreepayments.com/guides/unionpay/testing/javascript/v3
  */
-public class PaymentRequestCardEditorAutoAdvanceTest extends PaymentRequestTestBase {
-    public PaymentRequestCardEditorAutoAdvanceTest() {
-        super("payment_request_free_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestCardEditorAutoAdvanceTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_free_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -42,27 +57,33 @@
                 billingAddressId, "" /* serverId */));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void test14DigitsCreditCard()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Diners credit card.
-        final View focusedChildView = getCardEditorFocusedView();
-        setTextInCardEditorAndWait(new String[] {"3056 9309 0259 0"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        final View focusedChildView = mPaymentRequestTestRule.getCardEditorFocusedView();
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"3056 9309 0259 0"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '3056 9309 0259 00' is an invalid 14 digits card number.
-        setTextInCardEditorAndWait(new String[] {"3056 9309 0259 00"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"3056 9309 0259 00"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '3056 9309 0259 04' is a valid 14 digits card number.
-        setTextInCardEditorAndWait(new String[] {"3056 9309 0259 04"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() != focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"3056 9309 0259 04"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() != focusedChildView);
 
         // Request focus to card number field after auto advancing above.
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@@ -71,31 +92,38 @@
                 focusedChildView.requestFocus();
             }
         });
-        setTextInCardEditorAndWait(new String[] {"3056 9309 0259 041"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"3056 9309 0259 041"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void test15DigitsCreditCard()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // American Express credit card.
-        final View focusedChildView = getCardEditorFocusedView();
-        setTextInCardEditorAndWait(new String[] {"3782 822463 1000"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        final View focusedChildView = mPaymentRequestTestRule.getCardEditorFocusedView();
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"3782 822463 1000"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '3782 822463 10000' is an invalid 15 digits card number.
-        setTextInCardEditorAndWait(new String[] {"3782 822463 10000"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"3782 822463 10000"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '3782 822463 10005' is a valid 15 digits card number.
-        setTextInCardEditorAndWait(new String[] {"3782 822463 10005"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() != focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"3782 822463 10005"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() != focusedChildView);
 
         // Request focus to card number field after auto advancing above.
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@@ -104,39 +132,48 @@
                 focusedChildView.requestFocus();
             }
         });
-        setTextInCardEditorAndWait(new String[] {"3782 822463 10005 1"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(new String[] {"3782 822463 10005 1"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void test16DigitsCreditCard()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // DISCOVER, JCB, MASTERCARD, MIR and VISA cards have 16 digits. Takes VISA as test input
         // which has 13 digits valid card.
-        final View focusedChildView = getCardEditorFocusedView();
-        setTextInCardEditorAndWait(new String[] {"4012 8888 8888 "}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        final View focusedChildView = mPaymentRequestTestRule.getCardEditorFocusedView();
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"4012 8888 8888 "}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '4012 8888 8888 1' is a valid 13 digits card number.
-        setTextInCardEditorAndWait(new String[] {"4012 8888 8888 1"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"4012 8888 8888 1"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
-        setTextInCardEditorAndWait(new String[] {"4012 8888 8888 188"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"4012 8888 8888 188"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '4012 8888 8888 1880' is an invalid 16 digits card number.
-        setTextInCardEditorAndWait(new String[] {"4012 8888 8888 1880"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(new String[] {"4012 8888 8888 1880"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '4012 8888 8888 1881' is a valid 16 digits card number.
-        setTextInCardEditorAndWait(new String[] {"4012 8888 8888 1881"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() != focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(new String[] {"4012 8888 8888 1881"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() != focusedChildView);
 
         // Request focus to card number field after auto advancing above.
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@@ -145,38 +182,47 @@
                 focusedChildView.requestFocus();
             }
         });
-        setTextInCardEditorAndWait(new String[] {"4012 8888 8888 1881 1"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(new String[] {"4012 8888 8888 1881 1"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void test19DigitsCreditCard()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // UNIONPAY credit card.
-        final View focusedChildView = getCardEditorFocusedView();
-        setTextInCardEditorAndWait(new String[] {"6250 9410 0652 859"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        final View focusedChildView = mPaymentRequestTestRule.getCardEditorFocusedView();
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"6250 9410 0652 859"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '6250 9410 0652 8599' is a valid 16 digits card number.
-        setTextInCardEditorAndWait(new String[] {"6250 9410 0652 8599"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(new String[] {"6250 9410 0652 8599"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
-        setTextInCardEditorAndWait(new String[] {"6212 3456 7890 0000 00"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(new String[] {"6212 3456 7890 0000 00"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '6212 3456 7890 0000 001' is an invalid 19 digits card number.
-        setTextInCardEditorAndWait(new String[] {"6212 3456 7890 0000 001"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(new String[] {"6212 3456 7890 0000 001"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
 
         // '6212 3456 7890 0000 003' is a valid 19 digits card number.
-        setTextInCardEditorAndWait(new String[] {"6212 3456 7890 0000 003"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() != focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(new String[] {"6212 3456 7890 0000 003"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() != focusedChildView);
 
         // Request focus to card number field after auto advancing above.
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@@ -185,8 +231,9 @@
                 focusedChildView.requestFocus();
             }
         });
-        setTextInCardEditorAndWait(
-                new String[] {"6212 3456 7890 0000 0031"}, getEditorTextUpdate());
-        assertTrue(getCardEditorFocusedView() == focusedChildView);
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"6212 3456 7890 0000 0031"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getCardEditorFocusedView() == focusedChildView);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryNoCardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryNoCardTest.java
index 65e7b99d..ba4bd4e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryNoCardTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryNoCardTest.java
@@ -6,10 +6,19 @@
 
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,10 +27,15 @@
  * A payment integration test for checking whether user can make a payment via a credit card.
  * This user does not have a complete credit card on file.
  */
-public class PaymentRequestCcCanMakePaymentQueryNoCardTest extends PaymentRequestTestBase {
-    public PaymentRequestCcCanMakePaymentQueryNoCardTest() {
-        super("payment_request_can_make_payment_query_cc_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestCcCanMakePaymentQueryNoCardTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_can_make_payment_query_cc_test.html", this);
 
     @Override
     public void onMainActivityStarted() throws InterruptedException, ExecutionException,
@@ -33,11 +47,13 @@
                 R.drawable.pr_visa, "" /* billingAddressId */, "" /* serverId */));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testCannotMakePayment() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        openPageAndClickBuyAndWait(getCanMakePaymentQueryResponded());
-        expectResultContains(new String[]{"false"});
+    public void testCannotMakePayment()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(
+                mPaymentRequestTestRule.getCanMakePaymentQueryResponded());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"false"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsAndFreeShippingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsAndFreeShippingTest.java
index 2a06e94..98b2821 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsAndFreeShippingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsAndFreeShippingTest.java
@@ -7,12 +7,22 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -21,12 +31,15 @@
  * A payment integration test for a merchant that requests a payer name, an email address and
  * a phone number and provides free shipping regardless of address.
  */
-public class PaymentRequestContactDetailsAndFreeShippingTest extends PaymentRequestTestBase {
-    public PaymentRequestContactDetailsAndFreeShippingTest() {
-        // This merchant requests an email address and a phone number and provides free shipping
-        // worldwide.
-        super("payment_request_contact_details_and_free_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestContactDetailsAndFreeShippingTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule = new PaymentRequestTestRule(
+            "payment_request_contact_details_and_free_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -46,16 +59,21 @@
      * Submit the payer name, email address, phone number and shipping address to the merchant when
      * the user clicks "Pay."
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "jon.doe@google.com", "+15555555555",
-                "Jon Doe", "4111111111111111", "12", "2050", "visa", "123", "Google", "340 Main St",
-                "CA", "Los Angeles", "90291", "US", "en", "freeShippingOption"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Jon Doe", "jon.doe@google.com", "+15555555555", "Jon Doe",
+                        "4111111111111111", "12", "2050", "visa", "123", "Google", "340 Main St",
+                        "CA", "Los Angeles", "90291", "US", "en", "freeShippingOption"});
     }
 
     /**
@@ -63,19 +81,27 @@
      * and a shipping address results in the appropriate metric being logged in the
      * PaymentRequest.RequestedInformation histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == (PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL
-                    | PaymentRequestMetrics.REQUESTED_INFORMATION_PHONE
-                    | PaymentRequestMetrics.REQUESTED_INFORMATION_SHIPPING
-                    | PaymentRequestMetrics.REQUESTED_INFORMATION_NAME) ? 1 : 0),
+            Assert.assertEquals(
+                    (i
+                                            == (PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL
+                                                       | PaymentRequestMetrics
+                                                                 .REQUESTED_INFORMATION_PHONE
+                                                       | PaymentRequestMetrics
+                                                                 .REQUESTED_INFORMATION_SHIPPING
+                                                       | PaymentRequestMetrics
+                                                                 .REQUESTED_INFORMATION_NAME)
+                                    ? 1
+                                    : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java
index 92d772f9f..b786557 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java
@@ -4,14 +4,27 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,11 +32,15 @@
 /**
  * A payment integration test for a merchant that requests contact details.
  */
-public class PaymentRequestContactDetailsTest extends PaymentRequestTestBase {
-    public PaymentRequestContactDetailsTest() {
-        // The merchant requests a payer name, a phone number and an email address.
-        super("payment_request_contact_details_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestContactDetailsTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_contact_details_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -54,210 +71,249 @@
                 "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
                 "jon.doe@google.com", "en-US"));
 
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
     /** Provide the existing valid payer name, phone number and email address to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "+15555555555", "jon.doe@google.com"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Jon Doe", "+15555555555", "jon.doe@google.com"});
     }
 
     /** Attempt to add invalid contact information and cancel the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddInvalidContactAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"", "+++", "jane.jones"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(new String[] {"", "+++", "jane.jones"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Add new payer name, phone number and email address and provide that to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddContactAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Jane Jones", "999-999-9999", "jane.jones@google.com"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Jane Jones", "999-999-9999", "jane.jones@google.com"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"Jane Jones", "+19999999999", "jane.jones@google.com"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Jane Jones", "+19999999999", "jane.jones@google.com"});
     }
 
     /** Quickly pressing on "add contact info" and then [X] should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickAddContactAndCloseShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "add contact info" and then [X].
-        int callCount = getReadyToEdit().getCallCount();
+        int callCount = mPaymentRequestTestRule.getReadyToEdit().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getContactDetailsSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
             }
         });
-        getReadyToEdit().waitForCallback(callCount);
+        mPaymentRequestTestRule.getReadyToEdit().waitForCallback(callCount);
 
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing on [X] and then "add contact info" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickCloseAndAddContactShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on [X] and then "add contact info."
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getContactDetailsSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Test that going into the editor and cancelling will leave the row checked. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditContactAndCancelEditorShouldKeepContactSelected()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        expectContactDetailsRowIsSelected(0);
-        clickInContactInfoAndWait(R.id.payments_open_editor_pencil_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.expectContactDetailsRowIsSelected(0);
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_open_editor_pencil_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the editor.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Expect the row to still be selected in the Contact Details section.
-        expectContactDetailsRowIsSelected(0);
+        mPaymentRequestTestRule.expectContactDetailsRowIsSelected(0);
     }
 
     /** Test that going into the "add" flow and cancelling will leave existing row checked. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddContactAndCancelEditorShouldKeepContactSelected()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        expectContactDetailsRowIsSelected(0);
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.expectContactDetailsRowIsSelected(0);
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the editor.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Expect the existing row to still be selected in the Contact Details section.
-        expectContactDetailsRowIsSelected(0);
+        mPaymentRequestTestRule.expectContactDetailsRowIsSelected(0);
     }
 
     /** Quickly pressing on "add contact info" and then "cancel" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickAddContactAndCancelShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "add contact info" and then "cancel."
-        int callCount = getReadyToEdit().getCallCount();
+        int callCount = mPaymentRequestTestRule.getReadyToEdit().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getContactDetailsSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_secondary)
                         .performClick();
             }
         });
-        getReadyToEdit().waitForCallback(callCount);
+        mPaymentRequestTestRule.getReadyToEdit().waitForCallback(callCount);
 
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing on "cancel" and then "add contact info" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickCancelAndAddContactShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "cancel" and then "add contact info."
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_secondary)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getContactDetailsSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /**
      * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
      * to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSuggestionsDeduped()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(1, getNumberOfContactDetailSuggestions());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(1, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
     }
 
     /**
@@ -265,18 +321,25 @@
      * name results in the appropriate metric being logged in the
      * PaymentRequest.RequestedInformation histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == (PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL
-                    | PaymentRequestMetrics.REQUESTED_INFORMATION_PHONE
-                    | PaymentRequestMetrics.REQUESTED_INFORMATION_NAME) ? 1 : 0),
+            Assert.assertEquals(
+                    (i
+                                            == (PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL
+                                                       | PaymentRequestMetrics
+                                                                 .REQUESTED_INFORMATION_PHONE
+                                                       | PaymentRequestMetrics
+                                                                 .REQUESTED_INFORMATION_NAME)
+                                    ? 1
+                                    : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingMultipleAddressesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingMultipleAddressesTest.java
index cae5627..5ba62d3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingMultipleAddressesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingMultipleAddressesTest.java
@@ -6,10 +6,20 @@
 
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.ArrayList;
 import java.util.concurrent.ExecutionException;
@@ -19,7 +29,17 @@
  * A payment integration test for a merchant that requires shipping address to calculate shipping
  * and user that has 5 addresses stored in autofill settings.
  */
-public class PaymentRequestDynamicShippingMultipleAddressesTest extends PaymentRequestTestBase {
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestDynamicShippingMultipleAddressesTest
+        implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_dynamic_shipping_test.html", this);
+
     private static final AutofillProfile[] AUTOFILL_PROFILES = {
             // Incomplete profile (missing phone number)
             new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
@@ -61,11 +81,6 @@
     private int[] mCountsToSet;
     private int[] mDatesToSet;
 
-    public PaymentRequestDynamicShippingMultipleAddressesTest() {
-        // This merchant requests the shipping address first before providing any shipping options.
-        super("payment_request_dynamic_shipping_test.html");
-    }
-
     @Override
     public void onMainActivityStarted()
             throws InterruptedException, ExecutionException, TimeoutException {
@@ -88,6 +103,7 @@
      * suggestions are shown. They should be ordered by frecency and complete addresses should be
      * suggested first.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddressSuggestionOrdering()
@@ -100,18 +116,24 @@
         mCountsToSet = new int[] {20, 15, 10, 5, 1};
         mDatesToSet = new int[] {5000, 5000, 5000, 5000, 1};
 
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(4, getNumberOfShippingAddressSuggestions());
-        assertTrue(getShippingAddressSuggestionLabel(0).contains("Lisa Simpson"));
-        assertTrue(getShippingAddressSuggestionLabel(1).contains("Maggie Simpson"));
-        assertTrue(getShippingAddressSuggestionLabel(2).contains("Bart Simpson"));
-        assertTrue(getShippingAddressSuggestionLabel(3).contains("Marge Simpson"));
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfShippingAddressSuggestions());
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(0).contains(
+                "Lisa Simpson"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(1).contains(
+                "Maggie Simpson"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(2).contains(
+                "Bart Simpson"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(3).contains(
+                "Marge Simpson"));
     }
 
     /**
      * Make sure that a maximum of four profiles are shown to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddressSuggestionLimit()
@@ -123,19 +145,25 @@
         mCountsToSet = new int[] {20, 15, 10, 5, 2, 1};
         mDatesToSet = new int[] {5000, 5000, 5000, 5000, 2, 1};
 
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
         // Only four profiles should be suggested to the user.
-        assertEquals(4, getNumberOfShippingAddressSuggestions());
-        assertTrue(getShippingAddressSuggestionLabel(0).contains("Lisa Simpson"));
-        assertTrue(getShippingAddressSuggestionLabel(1).contains("Maggie Simpson"));
-        assertTrue(getShippingAddressSuggestionLabel(2).contains("Bart Simpson"));
-        assertTrue(getShippingAddressSuggestionLabel(3).contains("Marge Simpson"));
+        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfShippingAddressSuggestions());
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(0).contains(
+                "Lisa Simpson"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(1).contains(
+                "Maggie Simpson"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(2).contains(
+                "Bart Simpson"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(3).contains(
+                "Marge Simpson"));
     }
 
     /**
      * Make sure that only profiles with a street address are suggested to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddressSuggestion_OnlyIncludeProfilesWithStreetAddress()
@@ -148,20 +176,25 @@
         mCountsToSet = new int[] {15, 10, 5, 1};
         mDatesToSet = new int[] {5000, 5000, 5000, 1};
 
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
         // Only 3 profiles should be suggested, the two complete ones and the incomplete one that
         // has a street address.
-        assertEquals(3, getNumberOfShippingAddressSuggestions());
-        assertTrue(getShippingAddressSuggestionLabel(0).contains("Lisa Simpson"));
-        assertTrue(getShippingAddressSuggestionLabel(1).contains("Maggie Simpson"));
-        assertTrue(getShippingAddressSuggestionLabel(2).contains("Bart Simpson"));
+        Assert.assertEquals(3, mPaymentRequestTestRule.getNumberOfShippingAddressSuggestions());
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(0).contains(
+                "Lisa Simpson"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(1).contains(
+                "Maggie Simpson"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(2).contains(
+                "Bart Simpson"));
     }
 
     /**
      * Select a shipping address that the website refuses to accept, which should force the dialog
      * to show an error.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddresNotAcceptedByMerchant()
@@ -172,21 +205,25 @@
         mDatesToSet = new int[] {5000};
 
         // Click on the unacceptable shipping address.
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        assertTrue(getShippingAddressSuggestionLabel(0).contains(
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(0).contains(
                 AUTOFILL_PROFILES[3].getFullName()));
-        clickOnShippingAddressSuggestionOptionAndWait(0, getSelectionChecked());
+        mPaymentRequestTestRule.clickOnShippingAddressSuggestionOptionAndWait(
+                0, mPaymentRequestTestRule.getSelectionChecked());
 
         // The string should reflect the error sent from the merchant.
-        CharSequence actualString = getShippingAddressOptionRowAtIndex(0).getLabelText();
-        assertEquals("We do not ship to this address", actualString);
+        CharSequence actualString =
+                mPaymentRequestTestRule.getShippingAddressOptionRowAtIndex(0).getLabelText();
+        Assert.assertEquals("We do not ship to this address", actualString);
     }
 
     /**
      * Make sure the information required message has been displayed for incomplete profile
      * correctly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddressEditRequiredMessage()
@@ -197,13 +234,18 @@
         mCountsToSet = new int[] {15, 10, 5, 1};
         mDatesToSet = new int[] {5000, 5000, 5000, 1};
 
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
-        assertEquals(4, getNumberOfShippingAddressSuggestions());
-        assertTrue(getShippingAddressSuggestionLabel(0).contains("Phone number required"));
-        assertTrue(getShippingAddressSuggestionLabel(1).contains("Enter a valid address"));
-        assertTrue(getShippingAddressSuggestionLabel(2).contains("Name required"));
-        assertTrue(getShippingAddressSuggestionLabel(3).contains("More information required"));
+        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfShippingAddressSuggestions());
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(0).contains(
+                "Phone number required"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(1).contains(
+                "Enter a valid address"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(2).contains(
+                "Name required"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(3).contains(
+                "More information required"));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingSingleAddressTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingSingleAddressTest.java
index 14cff02..539f284 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingSingleAddressTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingSingleAddressTest.java
@@ -7,14 +7,24 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.FlakyTest;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -23,11 +33,15 @@
  * A payment integration test for a merchant that requires shipping address to calculate shipping
  * and user that has a single address stored in autofill settings.
  */
-public class PaymentRequestDynamicShippingSingleAddressTest extends PaymentRequestTestBase {
-    public PaymentRequestDynamicShippingSingleAddressTest() {
-        // This merchant requests the shipping address first before providing any shipping options.
-        super("payment_request_dynamic_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestDynamicShippingSingleAddressTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_dynamic_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -43,100 +57,132 @@
     }
 
     /** The shipping address should not be selected in UI by default. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddressNotSelected()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE, getSummarySectionButtonState());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE,
+                mPaymentRequestTestRule.getSummarySectionButtonState());
     }
 
     /** Expand the shipping address section, select an address, and click "Pay." */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSelectValidAddressAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Shipping Address).
-        expectPaymentMethodRowIsSelected(0);
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_first_radio_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa",
-                "123", "Google", "340 Main St", "CA", "Los Angeles", "90291", "+16502530000", "US",
-                "en", "californiaShippingOption"});
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jon Doe", "4111111111111111",
+                "12", "2050", "visa", "123", "Google", "340 Main St", "CA", "Los Angeles", "90291",
+                "+16502530000", "US", "en", "californiaShippingOption"});
     }
 
     /** Expand the shipping address section, select an address, edit it and click "Pay." */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSelectValidAddressEditItAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Shipping Address).
-        expectPaymentMethodRowIsSelected(0);
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_first_radio_button, getReadyToPay());
-        expectShippingAddressRowIsSelected(0);
-        clickInShippingAddressAndWait(R.id.payments_open_editor_pencil_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Jane Doe"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_open_editor_pencil_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Jane Doe"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
 
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jane Doe", "4111111111111111", "12", "2050", "visa",
-                "123", "Google", "340 Main St", "CA", "Los Angeles", "90291", "+16502530000", "US",
-                "en", "californiaShippingOption"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jane Doe", "4111111111111111",
+                "12", "2050", "visa", "123", "Google", "340 Main St", "CA", "Los Angeles", "90291",
+                "+16502530000", "US", "en", "californiaShippingOption"});
     }
 
     /** Expand the shipping address section, select address, edit but cancel editing, and "Pay". */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSelectValidAddressEditItAndCancelAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Shipping Address).
-        expectPaymentMethodRowIsSelected(0);
-        clickInShippingAddressAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_first_radio_button, getReadyToPay());
-        expectShippingAddressRowIsSelected(0);
-        clickInShippingAddressAndWait(R.id.payments_open_editor_pencil_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Jane Doe"}, getEditorTextUpdate());
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_open_editor_pencil_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Jane Doe"}, mPaymentRequestTestRule.getEditorTextUpdate());
         // Cancel the edit.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
 
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa",
-                "123", "Google", "340 Main St", "CA", "Los Angeles", "90291", "+16502530000", "US",
-                "en", "californiaShippingOption"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jon Doe", "4111111111111111",
+                "12", "2050", "visa", "123", "Google", "340 Main St", "CA", "Los Angeles", "90291",
+                "+16502530000", "US", "en", "californiaShippingOption"});
     }
 
     /** Attempt to add an invalid address and cancel the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddInvalidAddressAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Shipping Address).
-        expectPaymentMethodRowIsSelected(0);
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
 
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /**
@@ -144,137 +190,157 @@
      * @MediumTest
      * @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) // crbug.com/626289
      */
+    @Test
     @FlakyTest(message = "crbug.com/626289")
     @Feature({"Payments"})
     public void testAddAddressAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Bob", "Google", "1600 Amphitheatre Pkwy",
-                "Mountain View", "CA", "94043", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Bob", "Google", "1600 Amphitheatre Pkwy",
-                "Mountain View", "CA", "94043", "+16502530000"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Bob", "Google", "1600 Amphitheatre Pkwy", "Mountain View", "CA",
+                        "94043", "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Bob", "Google",
+                "1600 Amphitheatre Pkwy", "Mountain View", "CA", "94043", "+16502530000"});
     }
 
     /** Quickly pressing "add address" and then [X] should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickAddAddressAndCloseShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press "add address" and then [X].
-        int callCount = getReadyToEdit().getCallCount();
+        int callCount = mPaymentRequestTestRule.getReadyToEdit().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getShippingAddressSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
             }
         });
-        getReadyToEdit().waitForCallback(callCount);
+        mPaymentRequestTestRule.getReadyToEdit().waitForCallback(callCount);
 
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing [X] and then "add address" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickCloseAndAddAddressShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press [X] and then "add address."
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getShippingAddressSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing "add address" and then "cancel" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickAddAddressAndCancelShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press "add address" and then "cancel."
-        int callCount = getReadyToEdit().getCallCount();
+        int callCount = mPaymentRequestTestRule.getReadyToEdit().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getShippingAddressSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_secondary)
                         .performClick();
             }
         });
-        getReadyToEdit().waitForCallback(callCount);
+        mPaymentRequestTestRule.getReadyToEdit().waitForCallback(callCount);
 
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing on "cancel" and then "add address" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickCancelAndAddAddressShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "cancel" and then "add address."
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_secondary)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getShippingAddressSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndFreeShippingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndFreeShippingTest.java
index a8344960..d53e2fc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndFreeShippingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndFreeShippingTest.java
@@ -7,12 +7,22 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -21,11 +31,15 @@
  * A payment integration test for a merchant that requires and email address and provides free
  * shipping regardless of address.
  */
-public class PaymentRequestEmailAndFreeShippingTest extends PaymentRequestTestBase {
-    public PaymentRequestEmailAndFreeShippingTest() {
-        // This merchant requests and email address and provides free shipping worldwide.
-        super("payment_request_email_and_free_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestEmailAndFreeShippingTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_email_and_free_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -41,16 +55,20 @@
     }
 
     /** Submit the email and the shipping address to the merchant when the user clicks "Pay." */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"jon.doe@google.com", "Jon Doe", "4111111111111111",
-                "12", "2050", "visa", "123", "Google", "340 Main St", "CA", "Los Angeles", "90291",
-                "US", "en", "freeShippingOption"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"jon.doe@google.com", "Jon Doe",
+                "4111111111111111", "12", "2050", "visa", "123", "Google", "340 Main St", "CA",
+                "Los Angeles", "90291", "US", "en", "freeShippingOption"});
     }
 
     /**
@@ -58,17 +76,23 @@
      * results in the appropriate metric being logged in the PaymentRequest.RequestedInformation
      * histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == (PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL
-                    | PaymentRequestMetrics.REQUESTED_INFORMATION_SHIPPING) ? 1 : 0),
+            Assert.assertEquals(
+                    (i
+                                            == (PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL
+                                                       | PaymentRequestMetrics
+                                                                 .REQUESTED_INFORMATION_SHIPPING)
+                                    ? 1
+                                    : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndPhoneTest.java
index b6c0d52f..b8dc730 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndPhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndPhoneTest.java
@@ -4,13 +4,26 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,11 +31,15 @@
 /**
  * A payment integration test for a merchant that requests email address and a phone number.
  */
-public class PaymentRequestEmailAndPhoneTest extends PaymentRequestTestBase {
-    public PaymentRequestEmailAndPhoneTest() {
-        // This merchant request an email address and a phone number.
-        super("payment_request_email_and_phone_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestEmailAndPhoneTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_email_and_phone_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -53,78 +70,98 @@
                 "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
                 "jon.doe@google.com", "en-US"));
 
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
     /** Provide the existing valid email address and phone number to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"+15555555555", "jon.doe@google.com"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"+15555555555", "jon.doe@google.com"});
     }
 
     /** Attempt to add an invalid email address and phone number and cancel the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddInvalidEmailAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"-1-", "jane.jones"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"-1-", "jane.jones"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Add a new email address and phone number and provide that to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddEmailAndPhoneAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(
-                new String[] {"555-555-5555", "jane.jones@google.com"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"+15555555555", "jane.jones@google.com"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"555-555-5555", "jane.jones@google.com"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"+15555555555", "jane.jones@google.com"});
     }
 
     /**
      * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
      * to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSuggestionsDeduped()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(1, getNumberOfContactDetailSuggestions());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(1, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
     }
 
     /**
      * Test that starting a payment request that requires only the user's email address results in
      * the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testRequestedInformationMetric()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         int appropriateEnumValue = PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL
                 | PaymentRequestMetrics.REQUESTED_INFORMATION_PHONE;
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == (appropriateEnumValue) ? 1 : 0),
+            Assert.assertEquals((i == (appropriateEnumValue) ? 1 : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java
index afa3fab..a911019c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java
@@ -4,13 +4,26 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,11 +31,15 @@
 /**
  * A payment integration test for a merchant that requests email address.
  */
-public class PaymentRequestEmailTest extends PaymentRequestTestBase {
-    public PaymentRequestEmailTest() {
-        // This merchant request an email address.
-        super("payment_request_email_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestEmailTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_email_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -53,75 +70,93 @@
                 "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
                 "jon.doe@google.com", "en-US"));
 
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
     /** Provide the existing valid email address to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"jon.doe@google.com"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"jon.doe@google.com"});
     }
 
     /** Attempt to add an invalid email address and cancel the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddInvalidEmailAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"jane.jones"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"jane.jones"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Add a new email address and provide that to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddEmailAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"jane.jones@google.com"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(new String[] {"jane.jones@google.com"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"jane.jones@google.com"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"jane.jones@google.com"});
     }
 
     /**
      * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
      * to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSuggestionsDeduped()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(1, getNumberOfContactDetailSuggestions());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(1, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
     }
 
     /**
      * Test that starting a payment request that requires only the user's email address results in
      * the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL ? 1 : 0),
+            Assert.assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL ? 1 : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExpiredLocalCardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExpiredLocalCardTest.java
index 79e5154..1877d7f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExpiredLocalCardTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExpiredLocalCardTest.java
@@ -7,12 +7,22 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.Calendar;
 import java.util.concurrent.ExecutionException;
@@ -21,10 +31,15 @@
 /**
  * A payment integration test for a user that pays with an expired local credit card.
  */
-public class PaymentRequestExpiredLocalCardTest extends PaymentRequestTestBase {
-    public PaymentRequestExpiredLocalCardTest() {
-        super("payment_request_free_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestExpiredLocalCardTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_free_shipping_test.html", this);
 
     AutofillTestHelper mHelper;
     String mCreditCardId;
@@ -48,45 +63,52 @@
      * credit card is expired. Also tests that the user can pay once they have entered a new valid
      * expiration date and that merchant receives the updated data.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayWithExpiredCard_ValidExpiration()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInExpiredCardUnmaskDialogAndWait(
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInExpiredCardUnmaskDialogAndWait(
                 new int[] {R.id.expiration_month, R.id.expiration_year, R.id.card_unmask_input},
-                new String[] {"11", "26", "123"}, getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "4111111111111111", "11", "2026", "visa",
-                "123", "Google", "340 Main St", "CA", "Los Angeles", "90291", "US", "en",
-                "freeShippingOption"});
+                new String[] {"11", "26", "123"}, mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jon Doe", "4111111111111111",
+                "11", "2026", "visa", "123", "Google", "340 Main St", "CA", "Los Angeles", "90291",
+                "US", "en", "freeShippingOption"});
     }
 
     /**
      * Tests that the new expiration date entered in the unmasking prompt for an expired card
      * overwrites that card's old expiration date.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayWithExpiredCard_NewExpirationSaved()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInExpiredCardUnmaskDialogAndWait(
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInExpiredCardUnmaskDialogAndWait(
                 new int[] {R.id.expiration_month, R.id.expiration_year, R.id.card_unmask_input},
-                new String[] {"11", "26", "123"}, getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+                new String[] {"11", "26", "123"}, mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure the new expiration date was saved.
         CreditCard storedCard = mHelper.getCreditCard(mCreditCardId);
-        assertEquals("11", storedCard.getMonth());
-        assertEquals("2026", storedCard.getYear());
+        Assert.assertEquals("11", storedCard.getMonth());
+        Assert.assertEquals("2026", storedCard.getYear());
     }
 
     /**
      * Tests that it is not possible to add an expired card in a payment request.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCannotAddExpiredCard()
@@ -96,23 +118,30 @@
         Calendar now = Calendar.getInstance();
         if (now.get(Calendar.MONTH) == 0) return;
 
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Try to add an expired card.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
         // Set the expiration date to past month of the current year.
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {now.get(Calendar.MONTH) - 1, 0, 0}, getBillingAddressChangeProcessed());
-        setTextInCardEditorAndWait(
-                new String[] {"4111111111111111", "Jon Doe"}, getEditorTextUpdate());
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {now.get(Calendar.MONTH) - 1, 0, 0},
+                mPaymentRequestTestRule.getBillingAddressChangeProcessed());
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"4111111111111111", "Jon Doe"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
 
         // Set the expiration date to the current month of the current year.
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {now.get(Calendar.MONTH), 0, 0}, getExpirationMonthChange());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {now.get(Calendar.MONTH), 0, 0},
+                mPaymentRequestTestRule.getExpirationMonthChange());
 
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
     }
 
     /**
@@ -121,31 +150,36 @@
     // @MediumTest
     // @Feature({"Payments"})
     // See: crbug.com/687438.
+    @Test
     @DisabledTest
     public void testPromptErrorMessages()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Click pay to get to the card unmask prompt.
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
 
         // Set valid arguments.
-        setTextInExpiredCardUnmaskDialogAndWait(
+        mPaymentRequestTestRule.setTextInExpiredCardUnmaskDialogAndWait(
                 new int[] {R.id.expiration_month, R.id.expiration_year, R.id.card_unmask_input},
-                new String[] {"10", "26", "123"}, getUnmaskValidationDone());
-        assertTrue(getUnmaskPromptErrorMessage().equals(""));
+                new String[] {"10", "26", "123"},
+                mPaymentRequestTestRule.getUnmaskValidationDone());
+        Assert.assertTrue(mPaymentRequestTestRule.getUnmaskPromptErrorMessage().equals(""));
 
         // Set an invalid expiration month.
-        setTextInExpiredCardUnmaskDialogAndWait(
+        mPaymentRequestTestRule.setTextInExpiredCardUnmaskDialogAndWait(
                 new int[] {R.id.expiration_month, R.id.expiration_year, R.id.card_unmask_input},
-                new String[] {"99", "25", "123"}, getUnmaskValidationDone());
-        assertTrue(getUnmaskPromptErrorMessage().equals(
+                new String[] {"99", "25", "123"},
+                mPaymentRequestTestRule.getUnmaskValidationDone());
+        Assert.assertTrue(mPaymentRequestTestRule.getUnmaskPromptErrorMessage().equals(
                 "Check your expiration month and try again"));
 
         // Set an invalid expiration year.
-        setTextInExpiredCardUnmaskDialogAndWait(
+        mPaymentRequestTestRule.setTextInExpiredCardUnmaskDialogAndWait(
                 new int[] {R.id.expiration_month, R.id.expiration_year, R.id.card_unmask_input},
-                new String[] {"10", "14", "123"}, getUnmaskValidationDone());
-        assertTrue(getUnmaskPromptErrorMessage().equals(
+                new String[] {"10", "14", "123"},
+                mPaymentRequestTestRule.getUnmaskValidationDone());
+        Assert.assertTrue(mPaymentRequestTestRule.getUnmaskPromptErrorMessage().equals(
                 "Check your expiration year and try again"));
 
         // If the current date is in January skip this test. It is not possible to select an expired
@@ -155,19 +189,20 @@
             String twoDigitsYear = Integer.toString(now.get(Calendar.YEAR)).substring(2);
 
             // Set an invalid expiration year.
-            setTextInExpiredCardUnmaskDialogAndWait(
+            mPaymentRequestTestRule.setTextInExpiredCardUnmaskDialogAndWait(
                     new int[] {R.id.expiration_month, R.id.expiration_year, R.id.card_unmask_input},
                     new String[] {
                             Integer.toString(now.get(Calendar.MONTH) - 1), twoDigitsYear, "123"},
-                    getUnmaskValidationDone());
-            assertTrue(getUnmaskPromptErrorMessage().equals(
+                    mPaymentRequestTestRule.getUnmaskValidationDone());
+            Assert.assertTrue(mPaymentRequestTestRule.getUnmaskPromptErrorMessage().equals(
                     "Check your expiration date and try again"));
         }
 
         // Set valid arguments again.
-        setTextInExpiredCardUnmaskDialogAndWait(
+        mPaymentRequestTestRule.setTextInExpiredCardUnmaskDialogAndWait(
                 new int[] {R.id.expiration_month, R.id.expiration_year, R.id.card_unmask_input},
-                new String[] {"10", "26", "123"}, getUnmaskValidationDone());
-        assertTrue(getUnmaskPromptErrorMessage().equals(""));
+                new String[] {"10", "26", "123"},
+                mPaymentRequestTestRule.getUnmaskValidationDone());
+        Assert.assertTrue(mPaymentRequestTestRule.getUnmaskPromptErrorMessage().equals(""));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExtraShippingOptionsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExtraShippingOptionsTest.java
index e92bfa7..3ea9de7d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExtraShippingOptionsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExtraShippingOptionsTest.java
@@ -7,11 +7,20 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -20,11 +29,15 @@
  * A payment integration test for a merchant that provides shipping options, but does not request a
  * shipping address.
  */
-public class PaymentRequestExtraShippingOptionsTest extends PaymentRequestTestBase {
-    public PaymentRequestExtraShippingOptionsTest() {
-        // This merchant provides shipping options, but does not request a shipping address.
-        super("payment_request_extra_shipping_options_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestExtraShippingOptionsTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_extra_shipping_options_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -42,14 +55,19 @@
      * Submit the payment information without shipping address or shipping options to the merchant
      * when the user clicks "Pay."
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa",
-                "123", "Google", "340 Main St", "CA", "Los Angeles", "90291", "US", "en"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa", "123", "Google",
+                        "340 Main St", "CA", "Los Angeles", "90291", "US", "en"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFailCompleteTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFailCompleteTest.java
index b025e68..d879f73 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFailCompleteTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFailCompleteTest.java
@@ -7,11 +7,20 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,10 +28,15 @@
 /**
  * A payment integration test for a merchant that always fails to complete the transaction.
  */
-public class PaymentRequestFailCompleteTest extends PaymentRequestTestBase {
-    public PaymentRequestFailCompleteTest() {
-        super("payment_request_fail_complete_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestFailCompleteTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_fail_complete_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -36,14 +50,19 @@
                 billingAddressId, "" /* serverId */));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getResultReady());
-        clickAndWait(R.id.ok_button, getDismissed());
-        expectResultContains(new String[] {"Transaction failed"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getResultReady());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.ok_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Transaction failed"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFieldTrialTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFieldTrialTest.java
index 055856c..8fc0de71 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFieldTrialTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFieldTrialTest.java
@@ -4,12 +4,23 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -17,11 +28,15 @@
 /**
  * A payment integration test for the different fields trials affecting payment requests.
  */
-public class PaymentRequestFieldTrialTest extends PaymentRequestTestBase {
-    public PaymentRequestFieldTrialTest() {
-        // This merchant supports bobpay and credit cards payments.
-        super("payment_request_bobpay_and_cards_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestFieldTrialTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_bobpay_and_cards_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -37,38 +52,42 @@
      * Tests that the Payment Request is cancelled if the user has no credit card and the proper
      * command line flag is set.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     @CommandLineFlags.Add("enable-features=NoCreditCardAbort")
     public void testAbortIfNoCard_Enabled_NoApp()
             throws InterruptedException, ExecutionException, TimeoutException {
-        openPageAndClickBuyAndWait(getShowFailed());
-        expectResultContains(new String[] {"The payment method is not supported"});
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getShowFailed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"The payment method is not supported"});
     }
 
     /**
      * Tests that the Payment Request UI is shown even if the user has no credit card and the proper
      * command line flag is set, if they have another payment app that is accepted by the merchant.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     @CommandLineFlags.Add("enable-features=NoCreditCardAbort")
     public void testAbortIfNoCard_Enabled_WithApp()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
     }
 
     /**
      * Tests that the Payment Request UI is shown if the user has no credit card and the proper
      * command line flag is set.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     @CommandLineFlags.Add("disable-features=NoCreditCardAbort")
     public void testAbortIfNoCard_Disabled()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Check that the Payment Request UI is shown.
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFreeShippingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFreeShippingTest.java
index f494f4f..3fd3d47 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFreeShippingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFreeShippingTest.java
@@ -7,14 +7,24 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.FlakyTest;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -22,11 +32,15 @@
 /**
  * A payment integration test for a merchant that provides free shipping regardless of address.
  */
-public class PaymentRequestFreeShippingTest extends PaymentRequestTestBase {
-    public PaymentRequestFreeShippingTest() {
-        // This merchant provides free shipping worldwide.
-        super("payment_request_free_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestFreeShippingTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_free_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -42,203 +56,243 @@
     }
 
     /** Submit the shipping address to the merchant when the user clicks "Pay." */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa",
-                "123", "Google", "340 Main St", "CA", "Los Angeles", "90291", "US", "en",
-                "freeShippingOption"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jon Doe", "4111111111111111",
+                "12", "2050", "visa", "123", "Google", "340 Main St", "CA", "Los Angeles", "90291",
+                "US", "en", "freeShippingOption"});
     }
 
     /** Attempt to add an invalid address and cancel the transaction. */
+    @Test
     @MediumTest
     @FlakyTest(message = "crbug.com/673371")
     @Feature({"Payments"})
     public void testAddInvalidAddressAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Add a valid address and complete the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddAddressAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Bob", "Google", "1600 Amphitheatre Pkwy",
-                "Mountain View", "CA", "94043", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Bob", "Google", "1600 Amphitheatre Pkwy",
-                "Mountain View", "CA", "94043", "+16502530000"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Bob", "Google", "1600 Amphitheatre Pkwy", "Mountain View", "CA",
+                        "94043", "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Bob", "Google",
+                "1600 Amphitheatre Pkwy", "Mountain View", "CA", "94043", "+16502530000"});
     }
 
     /** Change the country in the spinner, add a valid address, and complete the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testChangeCountryAddAddressAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setSpinnerSelectionInEditorAndWait(0 /* Afghanistan */, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Alice", "Supreme Court", "Airport Road", "Kabul",
-                "1043", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionInEditorAndWait(
+                0 /* Afghanistan */, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {
+                        "Alice", "Supreme Court", "Airport Road", "Kabul", "1043", "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {
                 "Alice", "Supreme Court", "Airport Road", "Kabul", "1043", "+16502530000"});
     }
 
     /** Quickly pressing on "add address" and then [X] should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickAddAddressAndCloseShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "add address" and then [X].
-        int callCount = getReadyToEdit().getCallCount();
+        int callCount = mPaymentRequestTestRule.getReadyToEdit().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getShippingAddressSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
             }
         });
-        getReadyToEdit().waitForCallback(callCount);
+        mPaymentRequestTestRule.getReadyToEdit().waitForCallback(callCount);
 
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing on [X] and then "add address" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickCloseAndAddAddressShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on [X] and then "add address."
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getShippingAddressSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing on "add address" and then "cancel" should not crash. */
+    @Test
     @MediumTest
     @FlakyTest(message = "crbug.com/673371")
     @Feature({"Payments"})
     public void testQuickAddAddressAndCancelShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "add address" and then "cancel."
-        int callCount = getReadyToEdit().getCallCount();
+        int callCount = mPaymentRequestTestRule.getReadyToEdit().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getShippingAddressSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_secondary)
                         .performClick();
             }
         });
-        getReadyToEdit().waitForCallback(callCount);
+        mPaymentRequestTestRule.getReadyToEdit().waitForCallback(callCount);
 
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing on "cancel" and then "add address" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickCancelAndAddAddressShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "cancel" and then "add address."
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_secondary)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getShippingAddressSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /**
      * Test that starting a payment request that requires only the shipping address results in the
      * appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_SHIPPING ? 1 : 0),
+            Assert.assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_SHIPPING ? 1 : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIdTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIdTest.java
index 6e45b0acd..4e0192e9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIdTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIdTest.java
@@ -7,11 +7,20 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -20,10 +29,15 @@
  * A payment integration test for verifying the PaymentResponse contains the
  * free-form identifier specified in PaymentDetailsInit.
  */
-public class PaymentRequestIdTest extends PaymentRequestTestBase {
-    public PaymentRequestIdTest() {
-        super("payment_request_id_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestIdTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_id_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -41,14 +55,18 @@
      * Submit the payment information without shipping address or shipping options to the merchant
      * when the user clicks "Pay."
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPaymentResponse()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"my_payment_id"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"my_payment_id"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsAndFreeShippingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsAndFreeShippingTest.java
index a7b40bde..0c00131 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsAndFreeShippingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsAndFreeShippingTest.java
@@ -4,12 +4,25 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,13 +32,16 @@
  * a phone number and provides free shipping regardless of address, with the user having
  * incomplete information.
  */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
 public class PaymentRequestIncompleteContactDetailsAndFreeShippingTest
-        extends PaymentRequestTestBase {
-    public PaymentRequestIncompleteContactDetailsAndFreeShippingTest() {
-        // This merchant requests an email address and a phone number and provides free shipping
-        // worldwide.
-        super("payment_request_contact_details_and_free_shipping_test.html");
-    }
+        implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule = new PaymentRequestTestRule(
+            "payment_request_contact_details_and_free_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -37,68 +53,89 @@
                 "340 Main St", "CA", "Los Angeles", "", "90291", "", "US",
                 "" /* invalid phone number */, "jon.doe@google.com", "en-US"));
 
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
     /** Update the shipping address with valid data and see that the contacts section is updated. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditIncompleteShippingAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
-        assertEquals("Jon Doe\njon.doe@google.com\nPhone number required",
-                getContactDetailsSuggestionLabel(0));
+        Assert.assertEquals("Jon Doe\njon.doe@google.com\nPhone number required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(0));
 
-        assertTrue(getShippingAddressSuggestionLabel(0).contains("Phone number required"));
-        clickInShippingAddressAndWait(R.id.payments_first_radio_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Jon Doe", "Google", "340 Main St", "Los Angeles",
-                "CA", "90291", "650-253-0000"},
-                getEditorTextUpdate());
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressSuggestionLabel(0).contains(
+                "Phone number required"));
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Jon Doe", "Google", "340 Main St", "Los Angeles", "CA", "90291",
+                        "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
         // The contact is now complete, but not selected.
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyForInput());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyForInput());
         // We select it.
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals("Jon Doe\n+1 650-253-0000\njon.doe@google.com",
-                getContactDetailsSuggestionLabel(0));
-        clickInContactInfoAndWait(R.id.payments_first_radio_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals("Jon Doe\n+1 650-253-0000\njon.doe@google.com",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(0));
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToPay());
 
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "+16502530000", "jon.doe@google.com"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Jon Doe", "+16502530000", "jon.doe@google.com"});
     }
 
     /** Add a shipping address with valid data and see that the contacts section is updated. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditIncompleteShippingAndContactAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // There is an incomplete contact.
-        assertEquals("Jon Doe\njon.doe@google.com\nPhone number required",
-                getContactDetailsSuggestionLabel(0));
+        Assert.assertEquals("Jon Doe\njon.doe@google.com\nPhone number required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(0));
 
         // Add a new Shipping Address and see that the contact section updates.
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Jane Doe", "Edge Corp.", "111 Wall St.", "New York",
-                "NY", "10110", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyForInput());
-        assertEquals("Jon Doe\njon.doe@google.com\nPhone number required",
-                getContactDetailsSuggestionLabel(0));
-        assertEquals(
-                "Jane Doe\n+1 650-253-0000\nEmail required", getContactDetailsSuggestionLabel(1));
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Jane Doe", "Edge Corp.", "111 Wall St.", "New York", "NY", "10110",
+                        "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals("Jon Doe\njon.doe@google.com\nPhone number required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(0));
+        Assert.assertEquals("Jane Doe\n+1 650-253-0000\nEmail required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(1));
 
         // Now edit the first contact and pay.
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_first_radio_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Jon Doe", "650-253-0000", "jon.doe@google.com"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Jon Doe", "650-253-0000", "jon.doe@google.com"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "+16502530000", "jon.doe@google.com"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Jon Doe", "+16502530000", "jon.doe@google.com"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsTest.java
index b9aa2df..957cad6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsTest.java
@@ -4,13 +4,26 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,12 +32,15 @@
  * A payment integration test for a merchant that requests contact details from a user that has
  * incomplete contact details stored on disk.
  */
-public class PaymentRequestIncompleteContactDetailsTest extends PaymentRequestTestBase {
-    public PaymentRequestIncompleteContactDetailsTest() {
-        // This merchant requests both a phone number and an email address. We set the user's email
-        // address as invalid, so all tests start in "ready for input" state.
-        super("payment_request_contact_details_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestIncompleteContactDetailsTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_contact_details_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -35,69 +51,92 @@
                 "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "333-333-3333",
                 "jon.doe" /* invalid email address */, "en-US"));
 
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
     /** Attempt to update the contact information with invalid data and cancel the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditIncompleteContactAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Not ready to pay since Contact email is invalid.
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Contact Details).
-        expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
         // Updating contact with an invalid value and cancelling means we're still not
         // ready to pay (the value is still the original value).
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_first_radio_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"", "---", "jane.jones"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(new String[] {"", "---", "jane.jones"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
         // The section collapses and the [CHOOSE] button is active.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
-        assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE, getContactDetailsButtonState());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE,
+                mPaymentRequestTestRule.getContactDetailsButtonState());
 
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Attempt to add invalid contact info alongside the already invalid info, and cancel. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddIncompleteContactAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Not ready to pay since Contact email is invalid.
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Contact Details).
-        expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
         // Adding contact with an invalid value and cancelling means we're still not
         // ready to pay (the value is still the original value).
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"", "---", "jane.jones"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(new String[] {"", "---", "jane.jones"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
         // The section collapses and the [CHOOSE] button is active.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
-        assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE, getContactDetailsButtonState());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE,
+                mPaymentRequestTestRule.getContactDetailsButtonState());
 
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Update the contact information with valid data and provide that to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditIncompleteContactAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_first_radio_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Jon Doe", "555-555-5555", "jon.doe@google.com"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "+15555555555", "jon.doe@google.com"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Jon Doe", "555-555-5555", "jon.doe@google.com"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Jon Doe", "+15555555555", "jon.doe@google.com"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteEmailTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteEmailTest.java
index 7167818..afc378e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteEmailTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteEmailTest.java
@@ -4,13 +4,26 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,12 +32,15 @@
  * A payment integration test for a merchant that requests email address from a user that has
  * incomplete email address stored on disk.
  */
-public class PaymentRequestIncompleteEmailTest extends PaymentRequestTestBase {
-    public PaymentRequestIncompleteEmailTest() {
-        // This merchant requests an email address. We set the user's email as invalid, so all tests
-        // start in "ready for input" state.
-        super("payment_request_email_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestIncompleteEmailTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_email_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -35,67 +51,89 @@
                 "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
                 "jon.doe" /* invalid email address */, "en-US"));
 
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
     /** Attempt to update the email with invalid data and cancel the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditIncompleteEmailAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Not ready to pay since Contact email is invalid.
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Contact Details).
-        expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
         // Updating contact with an invalid value and cancelling means we're still not
         // ready to pay (the value is still the original value).
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_first_radio_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"gmail.com"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
-        assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE, getContactDetailsButtonState());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"gmail.com"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE,
+                mPaymentRequestTestRule.getContactDetailsButtonState());
 
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Attempt to add an invalid email alongside the already invalid data and cancel. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddIncompleteEmailAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Not ready to pay since Contact email is invalid.
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Contact Details).
-        expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
         // Updating contact with an invalid value and cancelling means we're still not
         // ready to pay (the value is still the original value).
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"gmail.com"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"gmail.com"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
         // The section collapses and the [CHOOSE] button is active.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
-        assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE, getContactDetailsButtonState());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE,
+                mPaymentRequestTestRule.getContactDetailsButtonState());
 
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Update the email with valid data and provide that to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditIncompleteEmailAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_first_radio_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"jon.doe@google.com"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"jon.doe@google.com"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"jon.doe@google.com"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"jon.doe@google.com"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompletePhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompletePhoneTest.java
index 1b123ef6..dc0921b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompletePhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompletePhoneTest.java
@@ -4,13 +4,26 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,12 +32,15 @@
  * A payment integration test for a merchant that requests phone number from a user that has
  * incomplete phone number stored on disk.
  */
-public class PaymentRequestIncompletePhoneTest extends PaymentRequestTestBase {
-    public PaymentRequestIncompletePhoneTest() {
-        // This merchant requests a phone number. We set the user's phone as invalid, so all tests
-        // start in "ready for input" state.
-        super("payment_request_phone_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestIncompletePhoneTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_phone_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -35,67 +51,89 @@
                 "340 Main St", "CA", "Los Angeles", "", "90291", "", "US",
                 "+++" /* invalid phone */, "jon.doe@gmail.com", "en-US"));
 
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
     /** Attempt to update the phone number with invalid data and cancel the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditIncompletePhoneAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Not ready to pay since Contact phone is invalid.
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Contact Details).
-        expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
         // Updating contact with an invalid value and cancelling means we're still not
         // ready to pay (the value is still the original value).
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_first_radio_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"---"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
-        assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE, getContactDetailsButtonState());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"---"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE,
+                mPaymentRequestTestRule.getContactDetailsButtonState());
 
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Attempt to add an invalid phone alongside the already invalid data and cancel. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddIncompletePhoneAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Not ready to pay since Contact phone is invalid.
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // Check that there is a selected payment method (makes sure we are not ready to pay because
         // of the Contact Details).
-        expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
         // Updating contact with an invalid value and cancelling means we're still not
         // ready to pay (the value is still the original value).
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"---"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"---"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
         // The section collapses and the [CHOOSE] button is active.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
-        assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE, getContactDetailsButtonState());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(PaymentRequestSection.EDIT_BUTTON_CHOOSE,
+                mPaymentRequestTestRule.getContactDetailsButtonState());
 
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Update the phone with valid data and provide that to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditIncompletePhoneAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_first_radio_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"514-555-5555"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"+15145555555"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"514-555-5555"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"+15145555555"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteServerCardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteServerCardTest.java
index de2141f..703de2e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteServerCardTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteServerCardTest.java
@@ -7,11 +7,20 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,12 +28,17 @@
 /**
  * A test for paying with an incomplete server card.
  */
-public class PaymentRequestIncompleteServerCardTest extends PaymentRequestTestBase {
-    private static final int FIRST_BILLING_ADDRESS = 0;
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestIncompleteServerCardTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_no_shipping_test.html", this);
 
-    public PaymentRequestIncompleteServerCardTest() {
-        super("payment_request_no_shipping_test.html");
-    }
+    private static final int FIRST_BILLING_ADDRESS = 0;
 
     @Override
     public void onMainActivityStarted()
@@ -39,19 +53,27 @@
     }
 
     /** Click [PAY] and dismiss the card unmask dialog. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testPayAndDontUnmask() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_first_radio_button, getReadyToEdit());
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {FIRST_BILLING_ADDRESS}, getBillingAddressChangeProcessed());
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_NEGATIVE, getReadyToPay());
-        clickAndWait(R.id.button_secondary, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+    public void testPayAndDontUnmask()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {FIRST_BILLING_ADDRESS},
+                mPaymentRequestTestRule.getBillingAddressChangeProcessed());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_NEGATIVE, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_secondary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestJourneyLoggerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestJourneyLoggerTest.java
index 58abf0d2..6172c6c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestJourneyLoggerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestJourneyLoggerTest.java
@@ -4,15 +4,28 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NO_INSTRUMENTS;
+
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -20,10 +33,15 @@
 /**
  * A payment integration test to validate the logging of Payment Request metrics.
  */
-public class PaymentRequestJourneyLoggerTest extends PaymentRequestTestBase {
-    public PaymentRequestJourneyLoggerTest() {
-        super("payment_request_metrics_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestJourneyLoggerTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_metrics_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -48,331 +66,390 @@
     /**
      * Expect that the number of shipping address suggestions was logged properly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNumberOfSuggestionsShown_ShippingAddress_Completed()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with a credit card.
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure the right number of suggestions were logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.Completed", 2));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.Completed", 2));
 
         // Make sure no adds, edits or changes were logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 0));
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 0));
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 0));
     }
 
     /**
      * Expect that the number of shipping address suggestions was logged properly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNumberOfSuggestionsShown_ShippingAddress_AbortedByUser()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Cancel the payment request.
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
 
         // Wait for the histograms to be logged.
         Thread.sleep(200);
 
         // Make sure the right number of suggestions were logged.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.UserAborted", 2));
 
         // Make sure no adds, edits or changes were logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.UserAborted", 0));
-        assertEquals(1,
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.UserAborted", 0));
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.UserAborted", 0));
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.UserAborted", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.UserAborted", 0));
     }
 
     /**
      * Expect that the NumberOfSelectionEdits histogram gets logged properly for shipping addresses.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNumberOfSelectionEdits_ShippingAddress_Completed()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with a credit card.
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Select the incomplete address and edit it.
-        clickOnShippingAddressSuggestionOptionAndWait(1, getReadyToEdit());
-        setTextInEditorAndWait(
+        mPaymentRequestTestRule.clickOnShippingAddressSuggestionOptionAndWait(
+                1, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
                 new String[] {"In Complete", "Google", "344 Main St", "CA", "Los Angeles"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure the edit was logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 1));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 1));
 
         // Since the edit was not for the default selection a change should be logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 1));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 1));
 
         // Make sure no add was logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 0));
     }
 
     /**
      * Expect that the NumberOfSelectionAdds histogram gets logged properly for shipping addresses.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNumberOfSelectionAdds_ShippingAddress_Completed()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with a credit card.
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Add a new shipping address.
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setSpinnerSelectionInEditorAndWait(0 /* Afghanistan */, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Alice", "Supreme Court", "Airport Road", "Kabul",
-                "1043", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionInEditorAndWait(
+                0 /* Afghanistan */, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {
+                        "Alice", "Supreme Court", "Airport Road", "Kabul", "1043", "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Complete the transaction.
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure the add was logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 1));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 1));
 
         // Make sure no edits or changes were logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 0));
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 0));
     }
 
     /**
      * Expect that the number of credit card suggestions was logged properly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNumberOfSuggestionsShown_CreditCards_Completed()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with a credit card.
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure the right number of suggestions were logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSuggestionsShown.CreditCards.Completed", 2));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSuggestionsShown.CreditCards.Completed", 2));
 
         // Make sure no adds, edits or changes were logged.
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                                "PaymentRequest.NumberOfSelectionAdds.CreditCards.Completed", 0));
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionChanges.CreditCards.Completed", 0));
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                                "PaymentRequest.NumberOfSelectionEdits.CreditCards.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.CreditCards.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionChanges.CreditCards.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.CreditCards.Completed", 0));
     }
 
     /**
      * Expect that the number of credit card suggestions was logged properly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNumberOfSuggestionsShown_CreditCards_AbortedByUser()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Cancel the payment request.
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
 
         // Wait for the histograms to be logged.
         Thread.sleep(200);
 
         // Make sure the right number of suggestions were logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSuggestionsShown.CreditCards.UserAborted", 2));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSuggestionsShown.CreditCards.UserAborted", 2));
 
         // Make sure no adds, edits or changes were logged.
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                                "PaymentRequest.NumberOfSelectionAdds.CreditCards.UserAborted", 0));
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionChanges.CreditCards.UserAborted", 0));
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionEdits.CreditCards.UserAborted", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.CreditCards.UserAborted", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionChanges.CreditCards.UserAborted", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.CreditCards.UserAborted", 0));
     }
 
     /**
      * Expect that the NumberOfSelectionAdds histogram gets logged properly for credit cards.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNumberOfSelectionAdds_CreditCards_Completed()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with a credit card.
-        triggerUIAndWait("ccBuy", getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
 
         // Add a new credit card.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {11, 1, 0}, getBillingAddressChangeProcessed());
-        setTextInCardEditorAndWait(
-                new String[] {"4111111111111111", "Jon Doe"}, getEditorTextUpdate());
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {11, 1, 0}, mPaymentRequestTestRule.getBillingAddressChangeProcessed());
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"4111111111111111", "Jon Doe"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Complete the transaction.
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure the add was logged.
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                                "PaymentRequest.NumberOfSelectionAdds.CreditCards.Completed", 1));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.CreditCards.Completed", 1));
 
         // Make sure no edits or changes were logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionChanges.CreditCards.Completed", 0));
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                                "PaymentRequest.NumberOfSelectionEdits.CreditCards.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionChanges.CreditCards.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.CreditCards.Completed", 0));
     }
 
     /**
      * Expect that no metric for contact info has been logged.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNoContactInfoHistogram()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with a credit card.
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure nothing was logged for contact info.
-        assertEquals(
-                0, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSuggestionsShown.ContactInfo.Completed", 2));
-        assertEquals(0, RecordHistogram.getHistogramValueCountForTesting(
-                                "PaymentRequest.NumberOfSelectionAdds.ContactInfo.Completed", 0));
-        assertEquals(
-                0, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionChanges.ContactInfo.Completed", 0));
-        assertEquals(0, RecordHistogram.getHistogramValueCountForTesting(
-                                "PaymentRequest.NumberOfSelectionEdits.ContactInfo.Completed", 0));
+        Assert.assertEquals(0,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSuggestionsShown.ContactInfo.Completed", 2));
+        Assert.assertEquals(0,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.ContactInfo.Completed", 0));
+        Assert.assertEquals(0,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionChanges.ContactInfo.Completed", 0));
+        Assert.assertEquals(0,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.ContactInfo.Completed", 0));
     }
 
     /**
      * Expect that that the journey metrics are logged correctly on a second consecutive payment
      * request.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testTwoTimes() throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with a credit card.
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure the right number of suggestions were logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.Completed", 2));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.Completed", 2));
 
         // Make sure no adds, edits or changes were logged.
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 0));
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 0));
-        assertEquals(
-                1, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 0));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 0));
 
         // Complete a second Payment Request with a credit card.
-        reTriggerUIAndWait("ccBuy", getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.reTriggerUIAndWait(
+                "ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure the right number of suggestions were logged.
-        assertEquals(
-                2, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.Completed", 2));
+        Assert.assertEquals(2,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.Completed", 2));
 
         // Make sure no adds, edits or changes were logged.
-        assertEquals(
-                2, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 0));
-        assertEquals(
-                2, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 0));
-        assertEquals(
-                2, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 0));
+        Assert.assertEquals(2,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionAdds.ShippingAddress.Completed", 0));
+        Assert.assertEquals(2,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionChanges.ShippingAddress.Completed", 0));
+        Assert.assertEquals(2,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSelectionEdits.ShippingAddress.Completed", 0));
     }
 
     /**
      * Expect that no journey metrics are logged if the payment request was not shown to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testNoShow() throws InterruptedException, ExecutionException, TimeoutException {
         // Android Pay is supported but no instruments are present.
-        installPaymentApp("https://android.com/pay", NO_INSTRUMENTS, DELAYED_RESPONSE);
-        openPageAndClickNodeAndWait("androidPayBuy", getShowFailed());
-        expectResultContains(new String[] {"The payment method is not supported"});
+        mPaymentRequestTestRule.installPaymentApp(
+                "https://android.com/pay", NO_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickNodeAndWait(
+                "androidPayBuy", mPaymentRequestTestRule.getShowFailed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"The payment method is not supported"});
 
         // Make sure that no journey metrics were logged.
-        assertEquals(0,
+        Assert.assertEquals(0,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.UserAborted", 2));
-        assertEquals(0,
+        Assert.assertEquals(0,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.OtherAborted", 2));
-        assertEquals(
-                0, RecordHistogram.getHistogramValueCountForTesting(
-                           "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.Completed", 2));
+        Assert.assertEquals(0,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.NumberOfSuggestionsShown.ShippingAddress.Completed", 2));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMetricsTest.java
index 80a1d21..f413b85e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMetricsTest.java
@@ -4,18 +4,33 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NO_INSTRUMENTS;
+
 import android.content.DialogInterface;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 
 import java.util.concurrent.ExecutionException;
@@ -24,10 +39,15 @@
 /**
  * A payment integration test to validate the logging of Payment Request metrics.
  */
-public class PaymentRequestMetricsTest extends PaymentRequestTestBase {
-    public PaymentRequestMetricsTest() {
-        super("payment_request_metrics_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestMetricsTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_metrics_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -45,63 +65,74 @@
     /**
      * Expect that the successful checkout funnel metrics are logged during a succesful checkout.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testSuccessCheckoutFunnel() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testSuccessCheckoutFunnel()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Initiate a payment request.
-        triggerUIAndWait("ccBuy", getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure sure that the "Initiated" and "Shown" events were logged.
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                "PaymentRequest.CheckoutFunnel.Initiated", 1));
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                "PaymentRequest.CheckoutFunnel.Shown", 1));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.CheckoutFunnel.Initiated", 1));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.CheckoutFunnel.Shown", 1));
 
         // Click the pay button.
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
 
         // Make sure sure that the "PayClicked" event was logged.
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                "PaymentRequest.CheckoutFunnel.PayClicked", 1));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.CheckoutFunnel.PayClicked", 1));
 
         // Unmask the credit card,
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Make sure sure that the "ReceivedInstrumentDetails" and "Completed" events were logged.
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                "PaymentRequest.CheckoutFunnel.ReceivedInstrumentDetails", 1));
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                "PaymentRequest.CheckoutFunnel.Completed", 1));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.CheckoutFunnel.ReceivedInstrumentDetails", 1));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.CheckoutFunnel.Completed", 1));
     }
 
     /**
      * Expect only the ABORT_REASON_ABORTED_BY_USER enum value gets logged when a user cancels a
      * Payment Request.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testAbortMetrics_AbortedByUser_CancelButton() throws InterruptedException,
-            ExecutionException, TimeoutException {
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+    public void testAbortMetrics_AbortedByUser_CancelButton()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Cancel the Payment Request.
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_secondary)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
 
-        assertOnlySpecificAbortMetricLogged(
+        mPaymentRequestTestRule.assertOnlySpecificAbortMetricLogged(
                 PaymentRequestMetrics.ABORT_REASON_ABORTED_BY_USER);
     }
 
@@ -109,18 +140,21 @@
      * Expect only the ABORT_REASON_ABORTED_BY_USER enum value gets logged when a user presses on
      * the [X] button in the Payment Request dialog.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testAbortMetrics_AbortedByUser_XButton() throws InterruptedException,
-            ExecutionException, TimeoutException {
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+    public void testAbortMetrics_AbortedByUser_XButton()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Press the [X] button.
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
 
-        assertOnlySpecificAbortMetricLogged(
+        mPaymentRequestTestRule.assertOnlySpecificAbortMetricLogged(
                 PaymentRequestMetrics.ABORT_REASON_ABORTED_BY_USER);
     }
 
@@ -128,25 +162,27 @@
      * Expect only the ABORT_REASON_ABORTED_BY_USER enum value gets logged when a user presses on
      * the back button on their phone during a Payment Request.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testAbortMetrics_AbortedByUser_BackButton() throws InterruptedException,
-            ExecutionException, TimeoutException {
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+    public void testAbortMetrics_AbortedByUser_BackButton()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Press the back button.
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI().getDialogForTest().onBackPressed();
+                mPaymentRequestTestRule.getPaymentRequestUI().getDialogForTest().onBackPressed();
             }
         });
-        getDismissed().waitForCallback(callCount);
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
 
-        assertOnlySpecificAbortMetricLogged(
+        mPaymentRequestTestRule.assertOnlySpecificAbortMetricLogged(
                 PaymentRequestMetrics.ABORT_REASON_ABORTED_BY_USER);
     }
 
@@ -154,17 +190,20 @@
      * Expect only the ABORT_REASON_MOJO_RENDERER_CLOSING enum value gets logged when a user closes
      * the tab during a Payment Request.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testAbortMetrics_AbortedByUser_TabClosed() throws InterruptedException,
-            ExecutionException, TimeoutException {
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+    public void testAbortMetrics_AbortedByUser_TabClosed()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Press the back button.
-        ChromeTabUtils.closeCurrentTab(getInstrumentation(), getActivity());
+        ChromeTabUtils.closeCurrentTab(InstrumentationRegistry.getInstrumentation(),
+                mPaymentRequestTestRule.getActivity());
 
-        assertOnlySpecificAbortMetricLogged(
+        mPaymentRequestTestRule.assertOnlySpecificAbortMetricLogged(
                 PaymentRequestMetrics.ABORT_REASON_MOJO_RENDERER_CLOSING);
     }
 
@@ -172,17 +211,18 @@
      * Expect only the ABORT_REASON_ABORTED_BY_MERCHANT enum value gets logged when a Payment
      * Request gets cancelled by the merchant.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testAbortMetrics_AbortedByMerchant() throws InterruptedException,
-            ExecutionException, TimeoutException {
-        triggerUIAndWait("ccBuy", getReadyToPay());
+    public void testAbortMetrics_AbortedByMerchant()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
 
         // Simulate an abort by the merchant.
-        clickNodeAndWait("abort", getDismissed());
-        expectResultContains(new String[] {"Abort"});
+        mPaymentRequestTestRule.clickNodeAndWait("abort", mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Abort"});
 
-        assertOnlySpecificAbortMetricLogged(
+        mPaymentRequestTestRule.assertOnlySpecificAbortMetricLogged(
                 PaymentRequestMetrics.ABORT_REASON_ABORTED_BY_MERCHANT);
     }
 
@@ -193,21 +233,26 @@
      * not accept credit cards). It should instead be logged as a reason why the Payment Request was
      * not shown to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testMetrics_NoMatchingPaymentMethod()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Android Pay is supported but no instruments are present.
-        installPaymentApp("https://android.com/pay", NO_INSTRUMENTS, DELAYED_RESPONSE);
-        openPageAndClickNodeAndWait("androidPayBuy", getShowFailed());
-        expectResultContains(new String[] {"The payment method is not supported"});
+        mPaymentRequestTestRule.installPaymentApp(
+                "https://android.com/pay", NO_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickNodeAndWait(
+                "androidPayBuy", mPaymentRequestTestRule.getShowFailed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"The payment method is not supported"});
 
         // Make sure that it is not logged as an abort.
-        assertOnlySpecificAbortMetricLogged(-1 /* none */);
+        mPaymentRequestTestRule.assertOnlySpecificAbortMetricLogged(-1 /* none */);
         // Make sure that it was logged as a reason why the Payment Request was not shown.
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                                "PaymentRequest.CheckoutFunnel.NoShow",
-                                PaymentRequestMetrics.NO_SHOW_NO_MATCHING_PAYMENT_METHOD));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.CheckoutFunnel.NoShow",
+                        PaymentRequestMetrics.NO_SHOW_NO_MATCHING_PAYMENT_METHOD));
     }
 
     /**
@@ -216,34 +261,42 @@
      * merchant only accepts payment methods we don't support. It should instead be logged as a
      * reason why the Payment Request was not shown to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testMetrics_NoSupportedPaymentMethod()
             throws InterruptedException, ExecutionException, TimeoutException {
-        openPageAndClickNodeAndWait("noSupported", getShowFailed());
-        expectResultContains(new String[] {"The payment method is not supported"});
+        mPaymentRequestTestRule.openPageAndClickNodeAndWait(
+                "noSupported", mPaymentRequestTestRule.getShowFailed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"The payment method is not supported"});
 
         // Make sure that it is not logged as an abort.
-        assertOnlySpecificAbortMetricLogged(-1 /* none */);
+        mPaymentRequestTestRule.assertOnlySpecificAbortMetricLogged(-1 /* none */);
         // Make sure that it was logged as a reason why the Payment Request was not shown.
-        assertEquals(1, RecordHistogram.getHistogramValueCountForTesting(
-                                "PaymentRequest.CheckoutFunnel.NoShow",
-                                PaymentRequestMetrics.NO_SHOW_NO_SUPPORTED_PAYMENT_METHOD));
+        Assert.assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "PaymentRequest.CheckoutFunnel.NoShow",
+                        PaymentRequestMetrics.NO_SHOW_NO_SUPPORTED_PAYMENT_METHOD));
     }
 
     /**
      * Expect only the SELECTED_METHOD_CREDIT_CARD enum value to be logged for the
      * "SelectedPaymentMethod" histogram when completing a Payment Request with a credit card.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testSelectedPaymentMethod_CreditCard() throws InterruptedException,
-            ExecutionException, TimeoutException {
+    public void testSelectedPaymentMethod_CreditCard()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with a credit card.
-        triggerUIAndWait("ccBuy", getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         assertOnlySpecificSelectedPaymentMethodMetricLogged(
                 PaymentRequestMetrics.SELECTED_METHOD_CREDIT_CARD);
@@ -253,14 +306,18 @@
      * Expect only the SELECTED_METHOD_ANDROID_PAY enum value to be logged for the
      * "SelectedPaymentMethod" histogram when completing a Payment Request with Android Pay.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testSelectedPaymentMethod_AndroidPay() throws InterruptedException,
-            ExecutionException, TimeoutException {
+    public void testSelectedPaymentMethod_AndroidPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with Android Pay.
-        installPaymentApp("https://android.com/pay", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        triggerUIAndWait("androidPayBuy", getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
+        mPaymentRequestTestRule.installPaymentApp(
+                "https://android.com/pay", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "androidPayBuy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
 
         assertOnlySpecificSelectedPaymentMethodMetricLogged(
                 PaymentRequestMetrics.SELECTED_METHOD_ANDROID_PAY);
@@ -270,18 +327,21 @@
      * Expect that the SkippedShow metric is logged when the UI directly goes
      * to the payment app UI during a Payment Request.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testMetrics_SkippedShow()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with Android Pay.
-        installPaymentApp("https://android.com/pay", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        triggerUIAndWait("androidPaySkipUiBuy", getResultReady());
+        mPaymentRequestTestRule.installPaymentApp(
+                "https://android.com/pay", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "androidPaySkipUiBuy", mPaymentRequestTestRule.getResultReady());
 
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CheckoutFunnel.SkippedShow", 1));
-        assertEquals(0,
+        Assert.assertEquals(0,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CheckoutFunnel.Shown", 1));
     }
@@ -290,19 +350,22 @@
      * Expect that the PaymentRequest UI is shown even if all the requirements are met to skip, if
      * the skip feature is disabled.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     @CommandLineFlags.Add({"disable-features=" + ChromeFeatureList.WEB_PAYMENTS_SINGLE_APP_UI_SKIP})
     public void testMetrics_SkippedShow_Disabled()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Complete a Payment Request with Android Pay.
-        installPaymentApp("https://android.com/pay", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        triggerUIAndWait("androidPaySkipUiBuy", getReadyToPay());
+        mPaymentRequestTestRule.installPaymentApp(
+                "https://android.com/pay", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "androidPaySkipUiBuy", mPaymentRequestTestRule.getReadyToPay());
 
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CheckoutFunnel.Shown", 1));
-        assertEquals(0,
+        Assert.assertEquals(0,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CheckoutFunnel.SkippedShow", 1));
     }
@@ -310,28 +373,33 @@
     /**
      * Expect that the "Shown" event is recorded only once.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShownLoggedOnlyOnce()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Initiate a payment request.
-        triggerUIAndWait("ccBuy", getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait("ccBuy", mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure sure that the "Shown" event was logged.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CheckoutFunnel.Shown", 1));
 
         // Add a shipping address, which triggers a second "Show".
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Seb Doe", "Google", "340 Main St", "Los Angeles",
-                "CA", "90291", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Seb Doe", "Google", "340 Main St", "Los Angeles", "CA", "90291",
+                        "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure "Shown" is still logged only once.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CheckoutFunnel.Shown", 1));
     }
@@ -344,7 +412,7 @@
      */
     private void assertOnlySpecificSelectedPaymentMethodMetricLogged(int paymentMethod) {
         for (int i = 0; i < PaymentRequestMetrics.SELECTED_METHOD_MAX; ++i) {
-            assertEquals((i == paymentMethod ? 1 : 0),
+            Assert.assertEquals((i == paymentMethod ? 1 : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.SelectedPaymentMethod", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMultipleContactDetailsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMultipleContactDetailsTest.java
index db3c743..9315d684 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMultipleContactDetailsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMultipleContactDetailsTest.java
@@ -4,12 +4,25 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.ArrayList;
 import java.util.concurrent.ExecutionException;
@@ -19,7 +32,16 @@
  * A payment integration test for a merchant that requests contact details and a user that has
  * multiple contact detail options.
  */
-public class PaymentRequestMultipleContactDetailsTest extends PaymentRequestTestBase {
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestMultipleContactDetailsTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_contact_details_test.html", this);
+
     private static final AutofillProfile[] AUTOFILL_PROFILES = {
             // 0 - Incomplete (no phone) profile.
             new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
@@ -90,11 +112,6 @@
     private int[] mCountsToSet;
     private int[] mDatesToSet;
 
-    public PaymentRequestMultipleContactDetailsTest() {
-        // The merchant requests a name, a phone number and an email address.
-        super("payment_request_contact_details_test.html");
-    }
-
     @Override
     public void onMainActivityStarted()
             throws InterruptedException, ExecutionException, TimeoutException {
@@ -111,7 +128,7 @@
             helper.setProfileUseStatsForTesting(guids.get(i), mCountsToSet[i], mDatesToSet[i]);
         }
 
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
     /**
@@ -119,6 +136,7 @@
      * are shown. They should be ordered by frecency and complete contact details should be
      * suggested first.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testContactDetailsSuggestionOrdering()
@@ -131,23 +149,25 @@
         mCountsToSet = new int[] {20, 15, 10, 5, 1};
         mDatesToSet = new int[] {5000, 5000, 5000, 5000, 1};
 
-        triggerUIAndWait(getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(4, getNumberOfContactDetailSuggestions());
-        assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
-                getContactDetailsSuggestionLabel(0));
-        assertEquals("Maggie Simpson\n555 123-4567\nmaggie@simpson.com",
-                getContactDetailsSuggestionLabel(1));
-        assertEquals("Bart Simpson\nbart@simpson.com\nPhone number required",
-                getContactDetailsSuggestionLabel(2));
-        assertEquals(
-                "Homer Simpson\n555 123-4567\nEmail required", getContactDetailsSuggestionLabel(3));
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
+        Assert.assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(0));
+        Assert.assertEquals("Maggie Simpson\n555 123-4567\nmaggie@simpson.com",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(1));
+        Assert.assertEquals("Bart Simpson\nbart@simpson.com\nPhone number required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(2));
+        Assert.assertEquals("Homer Simpson\n555 123-4567\nEmail required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(3));
     }
 
     /**
      * Make sure the information required message has been displayed for incomplete contact details
      * correctly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testContactDetailsEditRequiredMessage()
@@ -157,22 +177,24 @@
         mCountsToSet = new int[] {15, 10, 5, 1};
         mDatesToSet = new int[] {5000, 5000, 5000, 5000};
 
-        triggerUIAndWait(getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(4, getNumberOfContactDetailSuggestions());
-        assertEquals("Bart Simpson\nbart@simpson.com\nPhone number required",
-                getContactDetailsSuggestionLabel(0));
-        assertEquals(
-                "Homer Simpson\n555 123-4567\nEmail required", getContactDetailsSuggestionLabel(1));
-        assertEquals("555 123-4567\nmarge@simpson.com\nName required",
-                getContactDetailsSuggestionLabel(2));
-        assertEquals(
-                "Marge Simpson\nMore information required", getContactDetailsSuggestionLabel(3));
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
+        Assert.assertEquals("Bart Simpson\nbart@simpson.com\nPhone number required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(0));
+        Assert.assertEquals("Homer Simpson\n555 123-4567\nEmail required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(1));
+        Assert.assertEquals("555 123-4567\nmarge@simpson.com\nName required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(2));
+        Assert.assertEquals("Marge Simpson\nMore information required",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(3));
     }
 
     /**
      * Makes sure that suggestions that are subsets of other fields (empty values) are deduped.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testContactDetailsDedupe_EmptyFields()
@@ -187,19 +209,21 @@
         mCountsToSet = new int[] {1, 20, 15, 10, 5};
         mDatesToSet = new int[] {1000, 4000, 3000, 2000, 1000};
 
-        triggerUIAndWait(getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Only the original profile with all the fields should be suggested.
-        assertEquals(1, getNumberOfContactDetailSuggestions());
-        assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
-                getContactDetailsSuggestionLabel(0));
+        Assert.assertEquals(1, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
+        Assert.assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(0));
     }
 
     /**
      * Makes sure that suggestions where some fields values are equal but with different case are
      * deduped.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testContactDetailsDedupe_Capitalization()
@@ -210,17 +234,19 @@
         mCountsToSet = new int[] {15, 5};
         mDatesToSet = new int[] {5000, 2000};
 
-        triggerUIAndWait(getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(1, getNumberOfContactDetailSuggestions());
-        assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
-                getContactDetailsSuggestionLabel(0));
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(1, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
+        Assert.assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(0));
     }
 
     /**
      * Makes sure that suggestions where some fields values are subsets of the other are not
      * deduped.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testContactDetailsDontDedupe_FieldSubset()
@@ -232,12 +258,13 @@
         mCountsToSet = new int[] {15, 25};
         mDatesToSet = new int[] {5000, 7000};
 
-        triggerUIAndWait(getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(2, getNumberOfContactDetailSuggestions());
-        assertEquals("Lisa Simpson\n555 123-4567\nfakelisa@simpson.com",
-                getContactDetailsSuggestionLabel(0));
-        assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
-                getContactDetailsSuggestionLabel(1));
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(2, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
+        Assert.assertEquals("Lisa Simpson\n555 123-4567\nfakelisa@simpson.com",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(0));
+        Assert.assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
+                mPaymentRequestTestRule.getContactDetailsSuggestionLabel(1));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameAndFreeShippingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameAndFreeShippingTest.java
index e36b025c..7917fba5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameAndFreeShippingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameAndFreeShippingTest.java
@@ -7,12 +7,22 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -21,11 +31,15 @@
  * A payment integration test for a merchant that requests a payer name and provides free shipping
  * regardless of address.
  */
-public class PaymentRequestNameAndFreeShippingTest extends PaymentRequestTestBase {
-    public PaymentRequestNameAndFreeShippingTest() {
-        // This merchant requests a payer name and provides free shipping worldwide.
-        super("payment_request_name_and_free_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestNameAndFreeShippingTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_name_and_free_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -41,16 +55,20 @@
     }
 
     /** Submit the payer name and shipping address to the merchant when the user clicks "Pay." */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "Jon Doe", "4111111111111111", "12",
-                "2050", "visa", "123", "Google", "340 Main St", "CA", "Los Angeles", "90291", "US",
-                "en", "freeShippingOption"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jon Doe", "Jon Doe",
+                "4111111111111111", "12", "2050", "visa", "123", "Google", "340 Main St", "CA",
+                "Los Angeles", "90291", "US", "en", "freeShippingOption"});
     }
 
     /**
@@ -58,17 +76,23 @@
      * results in the appropriate metric being logged in the PaymentRequest.RequestedInformation
      * histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == (PaymentRequestMetrics.REQUESTED_INFORMATION_SHIPPING
-                    | PaymentRequestMetrics.REQUESTED_INFORMATION_NAME) ? 1 : 0),
+            Assert.assertEquals(
+                    (i
+                                            == (PaymentRequestMetrics.REQUESTED_INFORMATION_SHIPPING
+                                                       | PaymentRequestMetrics
+                                                                 .REQUESTED_INFORMATION_NAME)
+                                    ? 1
+                                    : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameTest.java
index bb731fe0..e653ece 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameTest.java
@@ -7,12 +7,22 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -20,11 +30,15 @@
 /**
  * A payment integration test for a merchant that requests payer name.
  */
-public class PaymentRequestNameTest extends PaymentRequestTestBase {
-    public PaymentRequestNameTest() {
-        // This merchant request a payer name.
-        super("payment_request_name_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestNameTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_name_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -60,74 +74,96 @@
     }
 
     /** Provide the existing valid payer name to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jon Doe"});
     }
 
     /** Attempt to add an invalid payer name and cancel the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddInvalidNameAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {""}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {""}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Add a new payer name and provide that to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddNameAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Jane Jones"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jane Jones"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Jane Jones"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jane Jones"});
     }
 
     /**
      * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
      * to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSuggestionsDeduped()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(1, getNumberOfContactDetailSuggestions());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(1, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
     }
 
     /**
      * Test that starting a payment request that requires only the user's payer name results in
      * the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_NAME ? 1 : 0),
+            Assert.assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_NAME ? 1 : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNoShippingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNoShippingTest.java
index 78d7e3f..486cd42f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNoShippingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNoShippingTest.java
@@ -4,18 +4,32 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DECEMBER;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.FIRST_BILLING_ADDRESS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NEXT_YEAR;
+
 import android.content.DialogInterface;
 import android.os.Build;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -23,10 +37,15 @@
 /**
  * A payment integration test for a merchant that does not require shipping address.
  */
-public class PaymentRequestNoShippingTest extends PaymentRequestTestBase {
-    public PaymentRequestNoShippingTest() {
-        super("payment_request_no_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestNoShippingTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_no_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -41,65 +60,84 @@
     }
 
     /** Click [X] to cancel payment. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testCloseDialog() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+    public void testCloseDialog()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Click [EDIT] to expand the dialog, then click [X] to cancel payment. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testEditAndCloseDialog() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickAndWait(R.id.button_secondary, getReadyForInput());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+    public void testEditAndCloseDialog()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_secondary, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Click [EDIT] to expand the dialog, then click [CANCEL] to cancel payment. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testEditAndCancelDialog() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        clickAndWait(R.id.button_secondary, getReadyForInput());
-        clickAndWait(R.id.button_secondary, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+    public void testEditAndCancelDialog()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_secondary, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_secondary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Click [PAY] and dismiss the card unmask dialog. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa",
-                "123"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa", "123"});
     }
 
     /** Click [PAY], type in "123" into the CVC dialog, then submit the payment. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testCancelUnmaskAndRetry()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_NEGATIVE, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa",
-                "123"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_NEGATIVE, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa", "123"});
     }
 
     /** Attempt to add an invalid credit card number and cancel payment. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddInvalidCardNumberAndCancel()
@@ -107,39 +145,53 @@
         // Attempting to add an invalid card and cancelling out of the flow will result in the user
         // still being ready to pay with the previously selected credit card.
         fillNewCardForm("123", "Bob", DECEMBER, NEXT_YEAR, FIRST_BILLING_ADDRESS);
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInCardEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Attempt to add a credit card with an empty name on card and cancel payment. */
+    @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP) // crbug.com/678983
     @Feature({"Payments"})
     public void testAddEmptyNameOnCardAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
         fillNewCardForm("5454-5454-5454-5454", "", DECEMBER, NEXT_YEAR, FIRST_BILLING_ADDRESS);
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInCardEditorAndWait(R.id.payments_edit_cancel_button, getReadyForInput());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Save a new card on disk and pay. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSaveNewCardAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
         fillNewCardForm("5454-5454-5454-5454", "Bob", DECEMBER, NEXT_YEAR, FIRST_BILLING_ADDRESS);
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"5454545454545454", "12", "Bob"});
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"5454545454545454", "12", "Bob"});
     }
 
     /** Use a temporary credit card to complete payment. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddTemporaryCardAndPay()
@@ -147,250 +199,287 @@
         fillNewCardForm("5454-5454-5454-5454", "Bob", DECEMBER, NEXT_YEAR, FIRST_BILLING_ADDRESS);
 
         // Uncheck the "Save this card on this device" checkbox, so the card is temporary.
-        selectCheckboxAndWait(R.id.payments_edit_checkbox, false, getReadyToEdit());
+        mPaymentRequestTestRule.selectCheckboxAndWait(
+                R.id.payments_edit_checkbox, false, mPaymentRequestTestRule.getReadyToEdit());
 
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"5454545454545454", "12", "Bob"});
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"5454545454545454", "12", "Bob"});
     }
 
     private void fillNewCardForm(String cardNumber, String nameOnCard, int month, int year,
             int billingAddress) throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInCardEditorAndWait(new String[] {cardNumber, nameOnCard}, getEditorTextUpdate());
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {month, year, billingAddress}, getBillingAddressChangeProcessed());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(new String[] {cardNumber, nameOnCard},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {month, year, billingAddress},
+                mPaymentRequestTestRule.getBillingAddressChangeProcessed());
     }
 
     /** Add a new card together with a new billing address and pay. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSaveNewCardAndNewBillingAddressAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInCardEditorAndWait(
-                new String[] {"5454 5454 5454 5454", "Bob"}, getEditorTextUpdate());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInCardEditorAndWait(
+                new String[] {"5454 5454 5454 5454", "Bob"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
 
         // Select December of next year for expiration and [Add address] in the billing address
         // dropdown.
         int addBillingAddress = 1;
-        setSpinnerSelectionsInCardEditorAndWait(
-                new int[] {DECEMBER, NEXT_YEAR, addBillingAddress}, getReadyToEdit());
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
+                new int[] {DECEMBER, NEXT_YEAR, addBillingAddress},
+                mPaymentRequestTestRule.getReadyToEdit());
 
-        setTextInEditorAndWait(new String[] {"Bob", "Google", "1600 Amphitheatre Pkwy",
-                "Mountain View", "CA", "94043", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Bob", "Google", "1600 Amphitheatre Pkwy", "Mountain View", "CA",
+                        "94043", "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToEdit());
 
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"5454545454545454", "12", "Bob", "Google",
-                "1600 Amphitheatre Pkwy", "Mountain View", "CA", "94043", "+16502530000"});
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"5454545454545454", "12", "Bob", "Google", "1600 Amphitheatre Pkwy",
+                        "Mountain View", "CA", "94043", "+16502530000"});
     }
 
     /** Quickly pressing on "add card" and then [X] should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickAddCardAndCloseShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "add card" and then [X].
-        int callCount = getReadyToEdit().getCallCount();
+        int callCount = mPaymentRequestTestRule.getReadyToEdit().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getPaymentMethodSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
             }
         });
-        getReadyToEdit().waitForCallback(callCount);
+        mPaymentRequestTestRule.getReadyToEdit().waitForCallback(callCount);
 
-        clickInCardEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing on [X] and then "add card" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickCloseAndAddCardShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on [X] and then "add card."
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getPaymentMethodSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing on "add card" and then "cancel" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickAddCardAndCancelShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "add card" and then "cancel."
-        int callCount = getReadyToEdit().getCallCount();
+        int callCount = mPaymentRequestTestRule.getReadyToEdit().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getPaymentMethodSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_secondary)
                         .performClick();
             }
         });
-        getReadyToEdit().waitForCallback(callCount);
+        mPaymentRequestTestRule.getReadyToEdit().waitForCallback(callCount);
 
-        clickInCardEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Quickly pressing on "cancel" and then "add card" should not crash. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickCancelAndAddCardShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Quickly press on "cancel" and then "add card."
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_secondary)
                         .performClick();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getPaymentMethodSectionForTest()
                         .findViewById(R.id.payments_add_option_button)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /**
      * Quickly dismissing the dialog (via Android's back button, for example) and then pressing on
      * "pay" should not crash.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickDismissAndPayShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Quickly dismiss and then press on "pay."
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI().getDialogForTest().onBackPressed();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI().getDialogForTest().onBackPressed();
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.button_primary)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /**
      * Quickly dismissing the dialog (via Android's back button, for example) and then pressing on
      * [X] should not crash.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickDismissAndCloseShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Quickly dismiss and then press on [X].
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI().getDialogForTest().onBackPressed();
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI().getDialogForTest().onBackPressed();
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /**
      * Quickly pressing on [X] and then dismissing the dialog (via Android's back button, for
      * example) should not crash.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testQuickCloseAndDismissShouldNotCrash()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Quickly press on [X] and then dismiss.
-        int callCount = getDismissed().getCallCount();
+        int callCount = mPaymentRequestTestRule.getDismissed().getCallCount();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getPaymentRequestUI()
+                mPaymentRequestTestRule.getPaymentRequestUI()
                         .getDialogForTest()
                         .findViewById(R.id.close_button)
                         .performClick();
-                getPaymentRequestUI().getDialogForTest().onBackPressed();
+                mPaymentRequestTestRule.getPaymentRequestUI().getDialogForTest().onBackPressed();
             }
         });
-        getDismissed().waitForCallback(callCount);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(callCount);
 
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /**
@@ -398,16 +487,17 @@
      * results in the appropriate metric being logged in the PaymentRequest.RequestedInformation
      * histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_NONE ? 1 : 0),
+            Assert.assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_NONE ? 1 : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndCardsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndCardsTest.java
index b83292ab..59133030 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndCardsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndCardsTest.java
@@ -4,14 +4,29 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NO_INSTRUMENTS;
+
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,10 +34,15 @@
 /**
  * A payment integration test for a merchant that requests payment via Bob Pay or cards.
  */
-public class PaymentRequestPaymentAppAndCardsTest extends PaymentRequestTestBase {
-    public PaymentRequestPaymentAppAndCardsTest() {
-        super("payment_request_bobpay_and_cards_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestPaymentAppAndCardsTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_bobpay_and_cards_test.html", this);
 
     @Override
     public void onMainActivityStarted() throws InterruptedException, ExecutionException,
@@ -45,10 +65,11 @@
      * If Bob Pay does not have any instruments, show [visa, mastercard]. Here the payment app
      * responds quickly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoInstrumentsInFastBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testNoInstrumentsInFastBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
         runTest(NO_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
@@ -56,10 +77,11 @@
      * If Bob Pay does not have any instruments, show [visa, mastercard]. Here the payment app
      * responds slowly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoInstrumentsInSlowBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testNoInstrumentsInSlowBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
         runTest(NO_INSTRUMENTS, DELAYED_RESPONSE);
     }
 
@@ -67,10 +89,11 @@
      * If Bob Pay has instruments, show [bobpay, visa, mastercard]. Here the payment app responds
      * quickly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testHaveInstrumentsInFastBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testHaveInstrumentsInFastBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
         runTest(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
@@ -78,81 +101,97 @@
      * If Bob Pay has instruments, show [bobpay, visa, mastercard]. Here the payment app responds
      * slowly.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testHaveInstrumentsInSlowBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testHaveInstrumentsInSlowBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
         runTest(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
     }
 
     /** Test that going into the editor and cancelling will leave the row checked. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditPaymentMethodAndCancelEditorShouldKeepCardSelected()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        expectPaymentMethodRowIsSelected(0);
-        clickInPaymentMethodAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the editor.
-        clickInCardEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Expect the existing row to still be selected in the Shipping Address section.
-        expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
     }
 
     /** Test that going into "add" flow editor and cancelling will leave existing row checked. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddPaymentMethodAndCancelEditorShouldKeepExistingCardSelected()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
-        expectPaymentMethodRowIsSelected(0);
-        clickInPaymentMethodAndWait(R.id.payments_open_editor_pencil_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_open_editor_pencil_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the editor.
-        clickInCardEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Expect the row to still be selected in the Shipping Address section.
-        expectPaymentMethodRowIsSelected(0);
+        mPaymentRequestTestRule.expectPaymentMethodRowIsSelected(0);
     }
 
     private void runTest(int instrumentPresence, int responseSpeed) throws InterruptedException,
             ExecutionException, TimeoutException  {
-        installPaymentApp(instrumentPresence, responseSpeed);
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.installPaymentApp(instrumentPresence, responseSpeed);
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Check the number of instruments.
-        assertEquals(
-                instrumentPresence == HAVE_INSTRUMENTS ? 3 : 2, getNumberOfPaymentInstruments());
+        Assert.assertEquals(instrumentPresence == HAVE_INSTRUMENTS ? 3 : 2,
+                mPaymentRequestTestRule.getNumberOfPaymentInstruments());
 
         // Check the labels of the instruments.
         int i = 0;
         if (instrumentPresence == HAVE_INSTRUMENTS) {
-            assertEquals("https://bobpay.com", getPaymentInstrumentLabel(i++));
+            Assert.assertEquals(
+                    "https://bobpay.com", mPaymentRequestTestRule.getPaymentInstrumentLabel(i++));
         }
         // \u0020\...\u2006 is four dots ellipsis.
-        assertEquals(
+        Assert.assertEquals(
                 "Visa\u0020\u0020\u2022\u2006\u2022\u2006\u2022\u2006\u2022\u20061111\nJon Doe",
-                getPaymentInstrumentLabel(i++));
-        assertEquals(
+                mPaymentRequestTestRule.getPaymentInstrumentLabel(i++));
+        Assert.assertEquals(
                 "MasterCard\u0020\u0020\u2022\u2006\u2022\u2006\u2022\u2006\u2022\u20065454\n"
-                + "Jon Doe\nBilling address required",
-                getPaymentInstrumentLabel(i++));
+                        + "Jon Doe\nBilling address required",
+                mPaymentRequestTestRule.getPaymentInstrumentLabel(i++));
 
         // Check the output of the selected instrument.
         if (instrumentPresence == HAVE_INSTRUMENTS) {
-            clickAndWait(R.id.button_primary, getDismissed());
-            expectResultContains(new String[]{"https://bobpay.com", "\"transaction\"", "1337"});
+            mPaymentRequestTestRule.clickAndWait(
+                    R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+            mPaymentRequestTestRule.expectResultContains(
+                    new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
         } else {
-            clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-            setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-            clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-            expectResultContains(new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa",
-                    "123"});
+            mPaymentRequestTestRule.clickAndWait(
+                    R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+            mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                    R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+            mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                    DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+            mPaymentRequestTestRule.expectResultContains(
+                    new String[] {"Jon Doe", "4111111111111111", "12", "2050", "visa", "123"});
         }
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppTest.java
index e7a9c23..07b0d79 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppTest.java
@@ -4,14 +4,28 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_CREATION;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NO_INSTRUMENTS;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedCallback;
 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryAddition;
 import org.chromium.chrome.browser.payments.PaymentRequestTestCommon.TestPay;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.WebContents;
 
 import java.util.Arrays;
@@ -22,57 +36,62 @@
 /**
  * A payment integration test for a merchant that requests payment via Bob Pay.
  */
-public class PaymentRequestPaymentAppTest extends PaymentRequestTestBase {
-    public PaymentRequestPaymentAppTest() {
-        super("payment_request_bobpay_test.html");
-    }
-
-    @Override
-    public void onMainActivityStarted() throws InterruptedException, ExecutionException,
-            TimeoutException {}
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestPaymentAppTest {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_bobpay_test.html");
 
     /** If no payment methods are supported, reject the show() promise. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoSupportedPaymentMethods() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        openPageAndClickBuyAndWait(getShowFailed());
-        expectResultContains(
-                new String[]{"show() rejected", "The payment method is not supported"});
+    public void testNoSupportedPaymentMethods()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getShowFailed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"show() rejected", "The payment method is not supported"});
     }
 
     /**
      * If Bob Pay does not have any instruments, reject the show() promise. Here Bob Pay responds to
      * Chrome immediately.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoInstrumentsInFastBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(NO_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        openPageAndClickBuyAndWait(getShowFailed());
-        expectResultContains(
-                new String[]{"show() rejected", "The payment method is not supported"});
+    public void testNoInstrumentsInFastBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(NO_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getShowFailed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"show() rejected", "The payment method is not supported"});
     }
 
     /**
      * If Bob Pay does not have any instruments, reject the show() promise. Here Bob Pay responds to
      * Chrome after a slight delay.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoInstrumentsInSlowBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(NO_INSTRUMENTS, DELAYED_RESPONSE);
-        openPageAndClickBuyAndWait(getShowFailed());
-        expectResultContains(
-                new String[]{"show() rejected", "The payment method is not supported"});
+    public void testNoInstrumentsInSlowBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(NO_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getShowFailed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"show() rejected", "The payment method is not supported"});
     }
 
     /**
      * If the payment app responds with more instruments after the UI has been dismissed, don't
      * crash.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPaymentWithInstrumentsAppResponseAfterDismissShouldNotCrash()
@@ -87,20 +106,23 @@
                 callback.onAllPaymentAppsCreated();
             }
         });
-        triggerUIAndWait(getReadyForInput());
-        clickAndWait(R.id.close_button, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
                 app.respond();
             }
         });
-        expectResultContains(new String[]{"show() rejected", "Request cancelled"});
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"show() rejected", "Request cancelled"});
     }
 
     /**
      * If the payment app responds with no instruments after the UI has been dismissed, don't crash.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPaymentAppNoInstrumentsResponseAfterDismissShouldNotCrash()
@@ -115,72 +137,84 @@
                 callback.onAllPaymentAppsCreated();
             }
         });
-        openPageAndClickBuyAndWait(getShowFailed());
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getShowFailed());
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
                 app.respond();
             }
         });
-        expectResultContains(
-                new String[]{"show() rejected", "The payment method is not supported"});
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"show() rejected", "The payment method is not supported"});
     }
 
     /**
      * If Bob Pay is supported and installed, user should be able to pay with it. Here Bob Pay
      * responds to Chrome immediately.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testPayViaFastBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[]{"https://bobpay.com", "\"transaction\"", "1337"});
+    public void testPayViaFastBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 
     /**
      * If Bob Pay is supported and installed, user should be able to pay with it. Here Bob Pay
      * responds to Chrome after a slight delay.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testPayViaSlowBobPay() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[]{"https://bobpay.com", "\"transaction\"", "1337"});
+    public void testPayViaSlowBobPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 
     /**
      * Test payment with a Bob Pay that is created with a delay, but responds immediately
      * to getInstruments.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaDelayedFastBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(
+        mPaymentRequestTestRule.installPaymentApp(
                 "https://bobpay.com", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE, DELAYED_CREATION);
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 
     /**
      * Test payment with a Bob Pay that is created with a delay, and responds slowly to
      * getInstruments.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaDelayedSlowBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(
+        mPaymentRequestTestRule.installPaymentApp(
                 "https://bobpay.com", HAVE_INSTRUMENTS, DELAYED_RESPONSE, DELAYED_CREATION);
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppUiSkipPreloadTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppUiSkipPreloadTest.java
index aee032c..cd64626 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppUiSkipPreloadTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppUiSkipPreloadTest.java
@@ -4,9 +4,22 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_CREATION;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -16,66 +29,75 @@
  * single payment app UI skip optimization in addition to preloading PaymentRequest even before the
  * "Buy" button is clicked.
  */
-public class PaymentRequestPaymentAppUiSkipPreloadTest extends PaymentRequestTestBase {
-    public PaymentRequestPaymentAppUiSkipPreloadTest() {
-        super("payment_request_bobpay_ui_skip_preload_test.html");
-    }
-
-    @Override
-    public void onMainActivityStarted()
-            throws InterruptedException, ExecutionException, TimeoutException {}
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestPaymentAppUiSkipPreloadTest {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_bobpay_ui_skip_preload_test.html");
 
     /**
      * If Bob Pay is supported and installed, user should be able to pay with it. Here Bob Pay
      * responds to Chrome immediately.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaFastBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        openPageAndClickBuyAndWait(getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 
     /**
      * If Bob Pay is supported and installed, user should be able to pay with it. Here Bob Pay
      * responds to Chrome after a slight delay.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaSlowBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
-        openPageAndClickBuyAndWait(getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 
     /**
      * Test payment with a Bob Pay that is created with a delay, but responds immediately
      * to getInstruments.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaDelayedFastBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(
+        mPaymentRequestTestRule.installPaymentApp(
                 "https://bobpay.com", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE, DELAYED_CREATION);
-        openPageAndClickBuyAndWait(getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 
     /**
      * Test payment with a Bob Pay that is created with a delay, and responds slowly to
      * getInstruments.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaDelayedSlowBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(
+        mPaymentRequestTestRule.installPaymentApp(
                 "https://bobpay.com", HAVE_INSTRUMENTS, DELAYED_RESPONSE, DELAYED_CREATION);
-        openPageAndClickBuyAndWait(getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppUiSkipTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppUiSkipTest.java
index 74f778c1..288f526f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppUiSkipTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppUiSkipTest.java
@@ -4,9 +4,22 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_CREATION;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DELAYED_RESPONSE;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -15,66 +28,75 @@
  * A payment integration test for a merchant that requests payment via Bob Pay when performing the
  * single payment app UI skip optimization.
  */
-public class PaymentRequestPaymentAppUiSkipTest extends PaymentRequestTestBase {
-    public PaymentRequestPaymentAppUiSkipTest() {
-        super("payment_request_bobpay_ui_skip_test.html");
-    }
-
-    @Override
-    public void onMainActivityStarted()
-            throws InterruptedException, ExecutionException, TimeoutException {}
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestPaymentAppUiSkipTest {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_bobpay_ui_skip_test.html");
 
     /**
      * If Bob Pay is supported and installed, user should be able to pay with it. Here Bob Pay
      * responds to Chrome immediately.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaFastBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
-        openPageAndClickBuyAndWait(getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 
     /**
      * If Bob Pay is supported and installed, user should be able to pay with it. Here Bob Pay
      * responds to Chrome after a slight delay.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaSlowBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
-        openPageAndClickBuyAndWait(getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, DELAYED_RESPONSE);
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 
     /**
      * Test payment with a Bob Pay that is created with a delay, but responds immediately
      * to getInstruments.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaDelayedFastBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(
+        mPaymentRequestTestRule.installPaymentApp(
                 "https://bobpay.com", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE, DELAYED_CREATION);
-        openPageAndClickBuyAndWait(getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 
     /**
      * Test payment with a Bob Pay that is created with a delay, and responds slowly to
      * getInstruments.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayViaDelayedSlowBobPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        installPaymentApp(
+        mPaymentRequestTestRule.installPaymentApp(
                 "https://bobpay.com", HAVE_INSTRUMENTS, DELAYED_RESPONSE, DELAYED_CREATION);
-        openPageAndClickBuyAndWait(getDismissed());
-        expectResultContains(new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://bobpay.com", "\"transaction\"", "1337"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppsSortingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppsSortingTest.java
index 8857866e..86a7bff 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppsSortingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppsSortingTest.java
@@ -4,16 +4,29 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedCallback;
 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryAddition;
 import org.chromium.chrome.browser.payments.PaymentRequestTestCommon.TestPay;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.WebContents;
 
 import java.util.Arrays;
@@ -22,10 +35,15 @@
 import java.util.concurrent.TimeoutException;
 
 /** A payment integration test that sorting payment apps and instruments by frecency. */
-public class PaymentRequestPaymentAppsSortingTest extends PaymentRequestTestBase {
-    public PaymentRequestPaymentAppsSortingTest() {
-        super("payment_request_alicepay_bobpay_charliepay_and_cards_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestPaymentAppsSortingTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule = new PaymentRequestTestRule(
+            "payment_request_alicepay_bobpay_charliepay_and_cards_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -41,6 +59,7 @@
                 "" /* serverId */));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPaymentAppsSortingByFrecency()
@@ -66,12 +85,16 @@
         String appBCharliePayId = appB.getAppIdentifier() + "https://charliepay.com";
 
         // The initial records for all payment methods are zeroes.
-        assertEquals(0, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appAAlicePayId));
-        assertEquals(0, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appABobPayId));
-        assertEquals(0, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appBCharliePayId));
-        assertEquals(0, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appAAlicePayId));
-        assertEquals(0, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appABobPayId));
-        assertEquals(0, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appBCharliePayId));
+        Assert.assertEquals(0, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appAAlicePayId));
+        Assert.assertEquals(0, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appABobPayId));
+        Assert.assertEquals(
+                0, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appBCharliePayId));
+        Assert.assertEquals(
+                0, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appAAlicePayId));
+        Assert.assertEquals(
+                0, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appABobPayId));
+        Assert.assertEquals(
+                0, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appBCharliePayId));
 
         // Sets Alice Pay use count and use date to 5. Sets Bob Pay use count and use date to 10.
         // Sets Charlie Pay use count and use date to 15.
@@ -82,57 +105,77 @@
         PaymentPreferencesUtil.setPaymentInstrumentUseCountForTest(appBCharliePayId, 15);
         PaymentPreferencesUtil.setPaymentInstrumentLastUseDate(appBCharliePayId, 15);
 
-        triggerUIAndWait(getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Checks Charlie Pay is listed at the first position.
-        assertEquals(4, getNumberOfPaymentInstruments());
-        assertEquals("https://charliepay.com", getPaymentInstrumentLabel(0));
-        assertEquals("https://bobpay.com", getPaymentInstrumentLabel(1));
-        assertEquals("https://alicepay.com", getPaymentInstrumentLabel(2));
-        assertEquals(
+        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfPaymentInstruments());
+        Assert.assertEquals(
+                "https://charliepay.com", mPaymentRequestTestRule.getPaymentInstrumentLabel(0));
+        Assert.assertEquals(
+                "https://bobpay.com", mPaymentRequestTestRule.getPaymentInstrumentLabel(1));
+        Assert.assertEquals(
+                "https://alicepay.com", mPaymentRequestTestRule.getPaymentInstrumentLabel(2));
+        Assert.assertEquals(
                 "Visa\u0020\u0020\u2022\u2006\u2022\u2006\u2022\u2006\u2022\u20061111\nJon Doe",
-                getPaymentInstrumentLabel(3));
+                mPaymentRequestTestRule.getPaymentInstrumentLabel(3));
 
         // Cancel the Payment Request.
-        clickAndWait(R.id.button_secondary, getDismissed());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_secondary, mPaymentRequestTestRule.getDismissed());
 
         // Checks the records for all payment instruments haven't been changed.
-        assertEquals(5, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appAAlicePayId));
-        assertEquals(10, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appABobPayId));
-        assertEquals(15, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appBCharliePayId));
-        assertEquals(5, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appAAlicePayId));
-        assertEquals(10, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appABobPayId));
-        assertEquals(15, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appBCharliePayId));
+        Assert.assertEquals(5, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appAAlicePayId));
+        Assert.assertEquals(10, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appABobPayId));
+        Assert.assertEquals(
+                15, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appBCharliePayId));
+        Assert.assertEquals(
+                5, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appAAlicePayId));
+        Assert.assertEquals(
+                10, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appABobPayId));
+        Assert.assertEquals(
+                15, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appBCharliePayId));
 
         // Sets Alice Pay use count and use date to 20.
         PaymentPreferencesUtil.setPaymentInstrumentUseCountForTest(appAAlicePayId, 20);
         PaymentPreferencesUtil.setPaymentInstrumentLastUseDate(appAAlicePayId, 20);
 
-        reTriggerUIAndWait("buy", getReadyToPay());
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.reTriggerUIAndWait("buy", mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Checks Alice Pay is listed at the first position. Checks Bob Pay is listed at the second
         // position together with Alice Pay since they come from the same app.
-        assertEquals(4, getNumberOfPaymentInstruments());
-        assertEquals("https://alicepay.com", getPaymentInstrumentLabel(0));
-        assertEquals("https://bobpay.com", getPaymentInstrumentLabel(1));
-        assertEquals("https://charliepay.com", getPaymentInstrumentLabel(2));
-        assertEquals(
+        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfPaymentInstruments());
+        Assert.assertEquals(
+                "https://alicepay.com", mPaymentRequestTestRule.getPaymentInstrumentLabel(0));
+        Assert.assertEquals(
+                "https://bobpay.com", mPaymentRequestTestRule.getPaymentInstrumentLabel(1));
+        Assert.assertEquals(
+                "https://charliepay.com", mPaymentRequestTestRule.getPaymentInstrumentLabel(2));
+        Assert.assertEquals(
                 "Visa\u0020\u0020\u2022\u2006\u2022\u2006\u2022\u2006\u2022\u20061111\nJon Doe",
-                getPaymentInstrumentLabel(3));
+                mPaymentRequestTestRule.getPaymentInstrumentLabel(3));
 
-        clickAndWait(R.id.button_primary, getDismissed());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
         // Checks Alice Pay is selected as the default payment method.
-        expectResultContains(new String[] {"https://alicepay.com", "\"transaction\"", "1337"});
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"https://alicepay.com", "\"transaction\"", "1337"});
 
         // Checks Alice Pay use count is increased by one after completing a payment request with
         // it.
-        assertEquals(21, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appAAlicePayId));
-        assertEquals(10, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appABobPayId));
-        assertEquals(15, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appBCharliePayId));
-        assertTrue(PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appAAlicePayId) > 20);
-        assertEquals(10, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appABobPayId));
-        assertEquals(15, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appBCharliePayId));
+        Assert.assertEquals(
+                21, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appAAlicePayId));
+        Assert.assertEquals(10, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appABobPayId));
+        Assert.assertEquals(
+                15, PaymentPreferencesUtil.getPaymentInstrumentUseCount(appBCharliePayId));
+        Assert.assertTrue(
+                PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appAAlicePayId) > 20);
+        Assert.assertEquals(
+                10, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appABobPayId));
+        Assert.assertEquals(
+                15, PaymentPreferencesUtil.getPaymentInstrumentLastUseDate(appBCharliePayId));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneAndFreeShippingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneAndFreeShippingTest.java
index 6730a12..bd46925 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneAndFreeShippingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneAndFreeShippingTest.java
@@ -7,12 +7,22 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -21,11 +31,15 @@
  * A payment integration test for a merchant that requests a phone number and provides free shipping
  * regardless of address.
  */
-public class PaymentRequestPhoneAndFreeShippingTest extends PaymentRequestTestBase {
-    public PaymentRequestPhoneAndFreeShippingTest() {
-        // This merchant requests a phone number and provides free shipping worldwide.
-        super("payment_request_phone_and_free_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestPhoneAndFreeShippingTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_phone_and_free_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -41,16 +55,20 @@
     }
 
     /** Submit the phone number and shipping address to the merchant when the user clicks "Pay." */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"+15555555555", "Jon Doe", "4111111111111111", "12",
-                "2050", "visa", "123", "Google", "340 Main St", "CA", "Los Angeles", "90291", "US",
-                "en", "freeShippingOption"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"+15555555555", "Jon Doe",
+                "4111111111111111", "12", "2050", "visa", "123", "Google", "340 Main St", "CA",
+                "Los Angeles", "90291", "US", "en", "freeShippingOption"});
     }
 
     /**
@@ -58,17 +76,23 @@
      * results in the appropriate metric being logged in the PaymentRequest.RequestedInformation
      * histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == (PaymentRequestMetrics.REQUESTED_INFORMATION_PHONE
-                    | PaymentRequestMetrics.REQUESTED_INFORMATION_SHIPPING) ? 1 : 0),
+            Assert.assertEquals(
+                    (i
+                                            == (PaymentRequestMetrics.REQUESTED_INFORMATION_PHONE
+                                                       | PaymentRequestMetrics
+                                                                 .REQUESTED_INFORMATION_SHIPPING)
+                                    ? 1
+                                    : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java
index 253ed69b4..70d10df7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java
@@ -4,13 +4,26 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.HAVE_INSTRUMENTS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.IMMEDIATE_RESPONSE;
+
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,11 +31,15 @@
 /**
  * A payment integration test for a merchant that requests phone number.
  */
-public class PaymentRequestPhoneTest extends PaymentRequestTestBase {
-    public PaymentRequestPhoneTest() {
-        // This merchant requests a phone number.
-        super("payment_request_phone_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestPhoneTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_phone_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -53,75 +70,93 @@
                 "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
                 "jon.doe@google.com", "en-US"));
 
-        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+        mPaymentRequestTestRule.installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
     /** Provide the existing valid phone number to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"+15555555555"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"+15555555555"});
     }
 
     /** Attempt to add an invalid phone number and cancel the transaction. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddInvalidPhoneAndCancel()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"+++"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"+++"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 
     /** Add a new phone number and provide that to the merchant. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddPhoneAndPay()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        clickInContactInfoAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"999-999-9999"}, getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"999-999-9999"}, mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
-        clickAndWait(R.id.button_primary, getDismissed());
-        expectResultContains(new String[] {"+19999999999"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"+19999999999"});
     }
 
     /**
      * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
      * to the user.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSuggestionsDeduped()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInContactInfoAndWait(R.id.payments_section, getReadyForInput());
-        assertEquals(1, getNumberOfContactDetailSuggestions());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInContactInfoAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(1, mPaymentRequestTestRule.getNumberOfContactDetailSuggestions());
     }
 
     /**
      * Test that starting a payment request that requires only the user's phone number results in
      * the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testRequestedInformationMetric() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
         // Start the Payment Request.
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that only the appropriate enum value was logged.
         for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
-            assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_PHONE ? 1 : 0),
+            Assert.assertEquals((i == PaymentRequestMetrics.REQUESTED_INFORMATION_PHONE ? 1 : 0),
                     RecordHistogram.getHistogramValueCountForTesting(
                             "PaymentRequest.RequestedInformation", i));
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRemoveBillingAddressTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRemoveBillingAddressTest.java
index 1d234c5..d7bf7f5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRemoveBillingAddressTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRemoveBillingAddressTest.java
@@ -4,14 +4,27 @@
 
 package org.chromium.chrome.browser.payments;
 
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.DECEMBER;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.FIRST_BILLING_ADDRESS;
+import static org.chromium.chrome.browser.payments.PaymentRequestTestRule.NEXT_YEAR;
+
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,10 +32,15 @@
 /**
  * A payment integration test for removing a billing address that is associated with a credit card.
  */
-public class PaymentRequestRemoveBillingAddressTest extends PaymentRequestTestBase {
-    public PaymentRequestRemoveBillingAddressTest() {
-        super("payment_request_no_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestRemoveBillingAddressTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_no_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -44,36 +62,44 @@
      * The billing address for the credit card has been removed. Using this card should bring up an
      * editor that requires selecting a new billing address.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testPayWithCard()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
 
         // Expand the payment section.
-        clickInPaymentMethodAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Selecting the credit card should bring up the editor.
-        clickInPaymentMethodAndWait(R.id.payments_first_radio_button, getReadyToEdit());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_first_radio_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Tapping "save" in the editor should trigger a validation error.
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getEditorValidationError());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getEditorValidationError());
 
         // Fix the validation error by selecting a billing address.
-        setSpinnerSelectionsInCardEditorAndWait(
+        mPaymentRequestTestRule.setSpinnerSelectionsInCardEditorAndWait(
                 new int[] {DECEMBER, NEXT_YEAR, FIRST_BILLING_ADDRESS},
-                getBillingAddressChangeProcessed());
+                mPaymentRequestTestRule.getBillingAddressChangeProcessed());
 
         // Tapping "save" in the editor now should close the editor dialog and enable the "pay"
         // button.
-        clickInCardEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInCardEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Pay with this card.
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
-        expectResultContains(new String[] {"4111111111111111", "Alice", "12", "123", "Jane Smith",
-                "Google", "1600 Amphitheatre Pkwy", "CA", "Mountain View", "94043", "US",
-                "+15555555555", "en-US"});
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"4111111111111111", "Alice",
+                "12", "123", "Jane Smith", "Google", "1600 Amphitheatre Pkwy", "CA",
+                "Mountain View", "94043", "US", "+15555555555", "en-US"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServerCardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServerCardTest.java
index 34ce490..5ac47af 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServerCardTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServerCardTest.java
@@ -7,11 +7,20 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,10 +28,15 @@
 /**
  * A test for using a server card in payments UI.
  */
-public class PaymentRequestServerCardTest extends PaymentRequestTestBase {
-    public PaymentRequestServerCardTest() {
-        super("payment_request_no_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestServerCardTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_no_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -38,14 +52,18 @@
     }
 
     /** Click [PAY] and dismiss the card unmask dialog. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testPayAndDontUnmask() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_NEGATIVE, getReadyToPay());
-        clickAndWait(R.id.close_button, getDismissed());
-        expectResultContains(new String[] {"Request cancelled"});
+    public void testPayAndDontUnmask()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_NEGATIVE, mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Request cancelled"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java
index 9f4e268d..dbba560a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java
@@ -6,7 +6,15 @@
 
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.WebContents;
 
 import java.util.ArrayList;
@@ -20,7 +28,16 @@
 /**
  * A payment integration test for service worker based payment apps.
  */
-public class PaymentRequestServiceWorkerPaymentAppTest extends PaymentRequestTestBase {
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestServiceWorkerPaymentAppTest {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_bobpay_test.html");
+
     /** Flag for installing a service worker payment app without any payment options. */
     private static final int NO_OPTIONS = 0;
 
@@ -30,10 +47,6 @@
     /** Flag for installing a service worker payment app with two options. */
     private static final int TWO_OPTIONS = 2;
 
-    public PaymentRequestServiceWorkerPaymentAppTest() {
-        super("payment_request_bobpay_test.html");
-    }
-
     /**
      * Installs a mock service worker based payment app for testing.
      *
@@ -71,36 +84,32 @@
                 });
     }
 
-    @Override
-    public void onMainActivityStarted() throws InterruptedException, ExecutionException,
-            TimeoutException {}
-
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testNoOptions() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testNoOptions() throws InterruptedException, ExecutionException, TimeoutException {
         installMockServiceWorkerPaymentApp(NO_OPTIONS);
-        openPageAndClickBuyAndWait(getShowFailed());
-        expectResultContains(
-                new String[]{"show() rejected", "The payment method is not supported"});
+        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getShowFailed());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"show() rejected", "The payment method is not supported"});
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testOneOption() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testOneOption() throws InterruptedException, ExecutionException, TimeoutException {
         installMockServiceWorkerPaymentApp(ONE_OPTION);
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // TODO(tommyt): crbug.com/669876. Expand this test as we implement more
         // service worker based payment app functionality.
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testTwoOptions() throws InterruptedException, ExecutionException,
-            TimeoutException {
+    public void testTwoOptions() throws InterruptedException, ExecutionException, TimeoutException {
         installMockServiceWorkerPaymentApp(TWO_OPTIONS);
-        triggerUIAndWait(getReadyForInput());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         // TODO(tommyt): crbug.com/669876. Expand this test as we implement more
         // service worker based payment app functionality.
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressChangeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressChangeTest.java
index e5bbe2b24..e99e614 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressChangeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressChangeTest.java
@@ -6,11 +6,20 @@
 
 import android.support.test.filters.MediumTest;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,13 +27,15 @@
 /**
  * A payment integration test for a merchant that requires shipping address to calculate shipping.
  */
-public class PaymentRequestShippingAddressChangeTest extends PaymentRequestTestBase {
-    public PaymentRequestShippingAddressChangeTest() {
-        // This merchant requests the shipping address first before providing any shipping options.
-        // The result printed from this site is the shipping address change, not the Payment
-        // Response.
-        super("payment_request_shipping_address_change_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestShippingAddressChangeTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_shipping_address_change_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -43,18 +54,22 @@
      * Tests the format of the shipping address that is sent to the merchant when the user changes
      * the shipping address selection.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddressChangeFormat()
             throws InterruptedException, ExecutionException, TimeoutException {
         // Select a shipping address and cancel out.
-        triggerUIAndWait(getReadyForInput());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickOnShippingAddressSuggestionOptionAndWait(0, getReadyForInput());
-        clickAndWait(R.id.close_button, getDismissed());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickOnShippingAddressSuggestionOptionAndWait(
+                0, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
 
         // The phone number should be formatted to the internation format.
-        expectResultContains(new String[] {"Jon Doe", "Google", "340 Main St", "CA", "Los Angeles",
-                "90291", "+16502530000", "US"});
+        mPaymentRequestTestRule.expectResultContains(new String[] {"Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "90291", "+16502530000", "US"});
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressTest.java
index e044e11..396761ca 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressTest.java
@@ -6,11 +6,21 @@
 
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -18,10 +28,15 @@
 /**
  * A payment integration test for shipping address labels.
  */
-public class PaymentRequestShippingAddressTest extends PaymentRequestTestBase {
-    public PaymentRequestShippingAddressTest() {
-        super("payment_request_free_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestShippingAddressTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_free_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -46,152 +61,187 @@
     }
 
     /** Verifies that the shipping address format in bottomsheet mode is as expected. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddressFormat_BottomSheet()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that the shipping label on the bottomsheet does not include the country.
-        assertTrue(getShippingAddressOptionRowAtIndex(0).getLabelText().toString().equals(
-                "Jon Doe\nGoogle, 340 Main St, Los Angeles, CA 90291\n555-555-5555"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressOptionRowAtIndex(0)
+                .getLabelText().toString().equals(
+                        "Jon Doe\nGoogle, 340 Main St, Los Angeles, CA 90291\n555-555-5555"));
     }
 
     /** Verifies that the shipping address format in fullsheet mode is as expected. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddressFormat_FullSheet()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Focus on a section other that shipping addresses to enter fullsheet mode.
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Make sure that the shipping label on the fullsheet does not include the country.
-        assertTrue(getShippingAddressOptionRowAtIndex(0).getLabelText().toString().equals(
-                "Jon Doe\nGoogle, 340 Main St, Los Angeles, CA 90291\n555-555-5555"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressOptionRowAtIndex(0)
+                .getLabelText().toString().equals(
+                            "Jon Doe\nGoogle, 340 Main St, Los Angeles, CA 90291\n555-555-5555"));
     }
 
     /** Verifies that the shipping address format in fullsheet mode is as expected. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddressFormat_Expanded()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Focus on the shipping addresses section to enter expanded mode.
-        clickInShippingAddressAndWait(R.id.payments_section, getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
 
         // Make sure that the shipping label in expanded mode includes the country.
-        assertTrue(getShippingAddressOptionRowAtIndex(0).getLabelText().toString().equals(
-                "Jon Doe\n"
-                + "Google, 340 Main St, Los Angeles, CA 90291, United States\n"
-                + "555-555-5555"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule.getShippingAddressOptionRowAtIndex(0)
+                        .getLabelText()
+                        .toString()
+                        .equals("Jon Doe\n"
+                                + "Google, 340 Main St, Los Angeles, CA 90291, United States\n"
+                                + "555-555-5555"));
 
         // Make sure that the second profile's shipping label also includes the country.
-        assertTrue(getShippingAddressOptionRowAtIndex(1).getLabelText().toString().equals(
-                "Fred Doe\n"
-                + "Google, 340 Main St, Los Angeles, CA 90291, United States\n"
-                + "555-555-5555"));
+        Assert.assertTrue(
+                mPaymentRequestTestRule.getShippingAddressOptionRowAtIndex(1)
+                        .getLabelText()
+                        .toString()
+                        .equals("Fred Doe\n"
+                                + "Google, 340 Main St, Los Angeles, CA 90291, United States\n"
+                                + "555-555-5555"));
     }
 
     /** Verifies that the shipping address format of a new address is as expected. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testShippingAddressFormat_NewAddress()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Add a shipping address.
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
-        setTextInEditorAndWait(new String[] {"Seb Doe", "Google", "340 Main St", "Los Angeles",
-                "CA", "90291", "650-253-0000"},
-                getEditorTextUpdate());
-        clickInEditorAndWait(R.id.payments_edit_done_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
+        mPaymentRequestTestRule.setTextInEditorAndWait(
+                new String[] {"Seb Doe", "Google", "340 Main St", "Los Angeles", "CA", "90291",
+                        "650-253-0000"},
+                mPaymentRequestTestRule.getEditorTextUpdate());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_done_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Make sure that the shipping label does not include the country.
-        assertTrue(getShippingAddressOptionRowAtIndex(0).getLabelText().toString().equals(
-                "Seb Doe\nGoogle, 340 Main St, Los Angeles, CA 90291\n+1 650-253-0000"));
+        Assert.assertTrue(mPaymentRequestTestRule.getShippingAddressOptionRowAtIndex(0)
+                .getLabelText().toString().equals(
+                        "Seb Doe\nGoogle, 340 Main St, Los Angeles, CA 90291\n+1 650-253-0000"));
     }
 
     /**
      * Test that going into the editor and clicking 'CANCEL' button to cancel editor will leave the
      * row checked.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditShippingAddressAndCancelEditorShouldKeepAddressSelected()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        expectShippingAddressRowIsSelected(0);
-        clickInShippingAddressAndWait(R.id.payments_open_editor_pencil_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_open_editor_pencil_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the editor by clicking 'CANCEL' button in the editor view.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Expect the row to still be selected in the Shipping Address section.
-        expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
     }
 
     /**
      * Test that going into the editor and clicking Android back button to cancel editor will leave
      * the row checked.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testEditShippingAddressAndClickAndroidBackButtonShouldKeepAddressSelected()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        expectShippingAddressRowIsSelected(0);
-        clickInShippingAddressAndWait(R.id.payments_open_editor_pencil_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_open_editor_pencil_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the editor by clicking Android back button.
-        clickAndroidBackButtonInEditorAndWait(getReadyToPay());
+        mPaymentRequestTestRule.clickAndroidBackButtonInEditorAndWait(
+                mPaymentRequestTestRule.getReadyToPay());
 
         // Expect the row to still be selected in the Shipping Address section.
-        expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
     }
 
     /**
      * Test that going into the "add" flow  and clicking 'CANCEL' button to cancel editor will
      * leave the existing row checked.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddShippingAddressAndCancelEditorShouldKeepAddressSelected()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        expectShippingAddressRowIsSelected(0);
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the editor by clicking 'CANCEL' button in the editor view.
-        clickInEditorAndWait(R.id.payments_edit_cancel_button, getReadyToPay());
+        mPaymentRequestTestRule.clickInEditorAndWait(
+                R.id.payments_edit_cancel_button, mPaymentRequestTestRule.getReadyToPay());
 
         // Expect the existing row to still be selected in the Shipping Address section.
-        expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
     }
 
     /**
      * Test that going into the "add" flow  and clicking Android back button to cancel editor will
      * leave the existing row checked.
      */
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testAddShippingAddressAndClickAndroidBackButtonShouldKeepAddressSelected()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        clickInShippingSummaryAndWait(R.id.payments_section, getReadyForInput());
-        expectShippingAddressRowIsSelected(0);
-        clickInShippingAddressAndWait(R.id.payments_add_option_button, getReadyToEdit());
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.clickInShippingSummaryAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.clickInShippingAddressAndWait(
+                R.id.payments_add_option_button, mPaymentRequestTestRule.getReadyToEdit());
 
         // Cancel the editor by clicking Android back button.
-        clickAndroidBackButtonInEditorAndWait(getReadyToPay());
+        mPaymentRequestTestRule.clickAndroidBackButtonInEditorAndWait(
+                mPaymentRequestTestRule.getReadyToPay());
 
         // Expect the existing row to still be selected in the Shipping Address section.
-        expectShippingAddressRowIsSelected(0);
+        mPaymentRequestTestRule.expectShippingAddressRowIsSelected(0);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowTwiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowTwiceTest.java
index db168cc47..c1470c1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowTwiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowTwiceTest.java
@@ -6,12 +6,22 @@
 
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -19,10 +29,15 @@
 /**
  * A payment integration test for a merchant that calls show() twice.
  */
-public class PaymentRequestShowTwiceTest extends PaymentRequestTestBase {
-    public PaymentRequestShowTwiceTest() {
-        super("payment_request_show_twice_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestShowTwiceTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_show_twice_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -36,22 +51,25 @@
                 billingAddressId, "" /* serverId */));
     }
 
+    @Test
     @MediumTest
     @Feature({"Payments"})
     public void testSecondShowRequestCancelled()
             throws InterruptedException, ExecutionException, TimeoutException {
-        triggerUIAndWait(getReadyToPay());
-        expectResultContains(new String[] {"Second request: AbortError: Request cancelled"});
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
+        mPaymentRequestTestRule.expectResultContains(
+                new String[] {"Second request: AbortError: Request cancelled"});
 
         // The web payments UI was not aborted.
-        assertOnlySpecificAbortMetricLogged(-1 /* none */);
+        mPaymentRequestTestRule.assertOnlySpecificAbortMetricLogged(-1 /* none */);
 
         // The UI was never shown due to another web payments UI already showing.
-        assertEquals(1,
+        Assert.assertEquals(1,
                 RecordHistogram.getHistogramValueCountForTesting(
                         "PaymentRequest.CheckoutFunnel.NoShow",
                         PaymentRequestMetrics.NO_SHOW_CONCURRENT_REQUESTS));
 
-        clickAndWait(R.id.close_button, getDismissed());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.close_button, mPaymentRequestTestRule.getDismissed());
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTabTest.java
index 3250176..6e292dd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTabTest.java
@@ -6,16 +6,26 @@
 
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.LoadUrlParams;
 
 import java.util.concurrent.ExecutionException;
@@ -25,10 +35,15 @@
  * A payment integration test for dismissing the dialog when the user switches tabs or navigates
  * elsewhere.
  */
-public class PaymentRequestTabTest extends PaymentRequestTestBase {
-    public PaymentRequestTabTest() {
-        super("payment_request_dynamic_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestTabTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_dynamic_shipping_test.html", this);
 
     @Override
     public void onMainActivityStarted()
@@ -43,55 +58,58 @@
     }
 
     /** If the user switches tabs somehow, the dialog is dismissed. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testDismissOnTabSwitch() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        assertEquals(0, getDismissed().getCallCount());
+    public void testDismissOnTabSwitch()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(0, mPaymentRequestTestRule.getDismissed().getCallCount());
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                getActivity().getTabCreator(false).createNewTab(
+                mPaymentRequestTestRule.getActivity().getTabCreator(false).createNewTab(
                         new LoadUrlParams("about:blank"), TabLaunchType.FROM_CHROME_UI, null);
             }
         });
-        getDismissed().waitForCallback(0);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(0);
     }
 
     /** If the user closes the tab, the dialog is dismissed. */
     //@MediumTest
     //@Feature({"Payments"})
     // Disabled due to recent flakiness: crbug.com/661450.
+    @Test
     @DisabledTest
-    public void testDismissOnTabClose() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        assertEquals(0, getDismissed().getCallCount());
+    public void testDismissOnTabClose()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(0, mPaymentRequestTestRule.getDismissed().getCallCount());
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                TabModel currentModel = getActivity().getCurrentTabModel();
+                TabModel currentModel = mPaymentRequestTestRule.getActivity().getCurrentTabModel();
                 TabModelUtils.closeCurrentTab(currentModel);
             }
         });
-        getDismissed().waitForCallback(0);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(0);
     }
 
     /** If the user navigates anywhere, the dialog is dismissed. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testDismissOnTabNavigate() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyForInput());
-        assertEquals(0, getDismissed().getCallCount());
+    public void testDismissOnTabNavigate()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(0, mPaymentRequestTestRule.getDismissed().getCallCount());
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                TabModel currentModel = getActivity().getCurrentTabModel();
+                TabModel currentModel = mPaymentRequestTestRule.getActivity().getCurrentTabModel();
                 TabModelUtils.getCurrentTab(currentModel).loadUrl(new LoadUrlParams("about:blank"));
             }
         });
-        getDismissed().waitForCallback(0);
+        mPaymentRequestTestRule.getDismissed().waitForCallback(0);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestUseStatsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestUseStatsTest.java
index a95219c8..f23aff8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestUseStatsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestUseStatsTest.java
@@ -7,11 +7,21 @@
 import android.content.DialogInterface;
 import android.support.test.filters.MediumTest;
 
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -20,11 +30,15 @@
  * A payment integration test to validate that the use stats of Autofill profiles and credit cards
  * updated when they are used to complete a Payment Request transaction.
  */
-public class PaymentRequestUseStatsTest extends PaymentRequestTestBase {
-    public PaymentRequestUseStatsTest() {
-        // This merchant provides free shipping worldwide.
-        super("payment_request_free_shipping_test.html");
-    }
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+public class PaymentRequestUseStatsTest implements MainActivityStartCallback {
+    @Rule
+    public PaymentRequestTestRule mPaymentRequestTestRule =
+            new PaymentRequestTestRule("payment_request_free_shipping_test.html", this);
 
     AutofillTestHelper mHelper;
     String mBillingAddressId;
@@ -47,29 +61,36 @@
     }
 
     /** Expect that using a profile and credit card to pay updates their usage stats. */
+    @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testLogProfileAndCreditCardUse() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        triggerUIAndWait(getReadyToPay());
+    public void testLogProfileAndCreditCardUse()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyToPay());
 
         // Get the current date value just before the start of the Payment Request.
         long timeBeforeRecord = mHelper.getCurrentDateForTesting();
 
         // Proceed with the payment.
-        clickAndWait(R.id.button_primary, getReadyForUnmaskInput());
-        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", getReadyToUnmask());
-        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, getDismissed());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
+        mPaymentRequestTestRule.setTextInCardUnmaskDialogAndWait(
+                R.id.card_unmask_input, "123", mPaymentRequestTestRule.getReadyToUnmask());
+        mPaymentRequestTestRule.clickCardUnmaskButtonAndWait(
+                DialogInterface.BUTTON_POSITIVE, mPaymentRequestTestRule.getDismissed());
 
         // Get the current date value just after the end of the Payment Request.
         long timeAfterRecord = mHelper.getCurrentDateForTesting();
 
         // Make sure the use counts were incremented and the use dates were set to the current time.
-        assertEquals(21, mHelper.getProfileUseCountForTesting(mBillingAddressId));
-        assertTrue(timeBeforeRecord <= mHelper.getProfileUseDateForTesting(mBillingAddressId));
-        assertTrue(timeAfterRecord >= mHelper.getProfileUseDateForTesting(mBillingAddressId));
-        assertEquals(2, mHelper.getCreditCardUseCountForTesting(mCreditCardId));
-        assertTrue(timeBeforeRecord <= mHelper.getCreditCardUseDateForTesting(mCreditCardId));
-        assertTrue(timeAfterRecord >= mHelper.getCreditCardUseDateForTesting(mCreditCardId));
+        Assert.assertEquals(21, mHelper.getProfileUseCountForTesting(mBillingAddressId));
+        Assert.assertTrue(
+                timeBeforeRecord <= mHelper.getProfileUseDateForTesting(mBillingAddressId));
+        Assert.assertTrue(
+                timeAfterRecord >= mHelper.getProfileUseDateForTesting(mBillingAddressId));
+        Assert.assertEquals(2, mHelper.getCreditCardUseCountForTesting(mCreditCardId));
+        Assert.assertTrue(
+                timeBeforeRecord <= mHelper.getCreditCardUseDateForTesting(mCreditCardId));
+        Assert.assertTrue(timeAfterRecord >= mHelper.getCreditCardUseDateForTesting(mCreditCardId));
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java
index ff933a0..2c386aa4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java
@@ -20,8 +20,7 @@
 public class ChannelDefinitionsTest {
     @Test
     public void testNoOverlapBetweenStartupAndLegacyChannelIds() throws Exception {
-        ChannelDefinitions channelDefinitions = new ChannelDefinitions();
-        assertThat(channelDefinitions.getStartupChannelIds(),
-                everyItem(not(isIn(channelDefinitions.getLegacyChannelIds()))));
+        assertThat(ChannelDefinitions.getStartupChannelIds(),
+                everyItem(not(isIn(ChannelDefinitions.getLegacyChannelIds()))));
     }
 }
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java
deleted file mode 100644
index 75fd5f4..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.notifications.channels;
-
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.junit.Assert.assertThat;
-
-import android.app.NotificationManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.BlockJUnit4ClassRunner;
-
-import org.chromium.chrome.test.util.browser.notifications.MockNotificationManagerProxy;
-
-/**
- * Unit tests for ChannelsInitializer.
- */
-@RunWith(BlockJUnit4ClassRunner.class)
-public class ChannelsInitializerTest {
-    private ChannelsInitializer mChannelsInitializer;
-    private MockNotificationManagerProxy mMockNotificationManager;
-
-    @Before
-    public void setUp() throws Exception {
-        mMockNotificationManager = new MockNotificationManagerProxy();
-        mChannelsInitializer =
-                new ChannelsInitializer(mMockNotificationManager, new ChannelDefinitions());
-    }
-
-    @Test
-    public void testDeleteLegacyChannels_noopOnCurrentDefinitions() throws Exception {
-        assertThat(mMockNotificationManager.getNotificationChannelIds(), is(empty()));
-
-        mChannelsInitializer.deleteLegacyChannels();
-        assertThat(mMockNotificationManager.getNotificationChannelIds(), is(empty()));
-
-        mChannelsInitializer.initializeStartupChannels();
-        assertThat(mMockNotificationManager.getNotificationChannelIds(), is(not(empty())));
-
-        int nChannels = mMockNotificationManager.getNotificationChannelIds().size();
-        mChannelsInitializer.deleteLegacyChannels();
-        assertThat(mMockNotificationManager.getNotificationChannelIds(), hasSize(nChannels));
-    }
-
-    @Test
-    public void testInitializeStartupChannels() throws Exception {
-        mChannelsInitializer.initializeStartupChannels();
-        assertThat(mMockNotificationManager.getNotificationChannelIds(),
-                containsInAnyOrder(ChannelDefinitions.CHANNEL_ID_BROWSER,
-                        ChannelDefinitions.CHANNEL_ID_DOWNLOADS,
-                        ChannelDefinitions.CHANNEL_ID_INCOGNITO,
-                        ChannelDefinitions.CHANNEL_ID_SITES, ChannelDefinitions.CHANNEL_ID_MEDIA));
-    }
-
-    @Test
-    public void testEnsureInitialized_browserChannel() throws Exception {
-        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_BROWSER);
-
-        assertThat(mMockNotificationManager.getChannels().size(), is(1));
-        ChannelDefinitions.Channel channel = mMockNotificationManager.getChannels().get(0);
-        assertThat(channel.mId, is(ChannelDefinitions.CHANNEL_ID_BROWSER));
-        assertThat(
-                channel.mNameResId, is(org.chromium.chrome.R.string.notification_category_browser));
-        assertThat(channel.mImportance, is(NotificationManager.IMPORTANCE_LOW));
-        assertThat(channel.mGroupId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-
-        assertThat(mMockNotificationManager.getNotificationChannelGroups().size(), is(1));
-        ChannelDefinitions.ChannelGroup group =
-                mMockNotificationManager.getNotificationChannelGroups().get(0);
-        assertThat(group.mId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-        assertThat(group.mNameResId,
-                is(org.chromium.chrome.R.string.notification_category_group_general));
-    }
-
-    @Test
-    public void testEnsureInitialized_downloadsChannel() throws Exception {
-        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_DOWNLOADS);
-
-        assertThat(mMockNotificationManager.getChannels().size(), is(1));
-        ChannelDefinitions.Channel channel = mMockNotificationManager.getChannels().get(0);
-        assertThat(channel.mId, is(ChannelDefinitions.CHANNEL_ID_DOWNLOADS));
-        assertThat(channel.mNameResId,
-                is(org.chromium.chrome.R.string.notification_category_downloads));
-        assertThat(channel.mImportance, is(NotificationManager.IMPORTANCE_LOW));
-        assertThat(channel.mGroupId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-
-        assertThat(mMockNotificationManager.getNotificationChannelGroups().size(), is(1));
-        ChannelDefinitions.ChannelGroup group =
-                mMockNotificationManager.getNotificationChannelGroups().get(0);
-        assertThat(group.mId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-        assertThat(group.mNameResId,
-                is(org.chromium.chrome.R.string.notification_category_group_general));
-    }
-
-    @Test
-    public void testEnsureInitialized_incognitoChannel() throws Exception {
-        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_INCOGNITO);
-
-        assertThat(mMockNotificationManager.getChannels().size(), is(1));
-        ChannelDefinitions.Channel channel = mMockNotificationManager.getChannels().get(0);
-        assertThat(channel.mId, is(ChannelDefinitions.CHANNEL_ID_INCOGNITO));
-        assertThat(channel.mNameResId,
-                is(org.chromium.chrome.R.string.notification_category_incognito));
-        assertThat(channel.mImportance, is(NotificationManager.IMPORTANCE_LOW));
-        assertThat(channel.mGroupId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-
-        assertThat(mMockNotificationManager.getNotificationChannelGroups().size(), is(1));
-        ChannelDefinitions.ChannelGroup group =
-                mMockNotificationManager.getNotificationChannelGroups().get(0);
-        assertThat(group.mId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-        assertThat(group.mNameResId,
-                is(org.chromium.chrome.R.string.notification_category_group_general));
-    }
-
-    @Test
-    public void testEnsureInitialized_mediaChannel() throws Exception {
-        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_MEDIA);
-
-        assertThat(mMockNotificationManager.getChannels().size(), is(1));
-        ChannelDefinitions.Channel channel = mMockNotificationManager.getChannels().get(0);
-        assertThat(channel.mId, is(ChannelDefinitions.CHANNEL_ID_MEDIA));
-        assertThat(
-                channel.mNameResId, is(org.chromium.chrome.R.string.notification_category_media));
-        assertThat(channel.mImportance, is(NotificationManager.IMPORTANCE_LOW));
-        assertThat(channel.mGroupId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-
-        assertThat(mMockNotificationManager.getNotificationChannelGroups().size(), is(1));
-        ChannelDefinitions.ChannelGroup group =
-                mMockNotificationManager.getNotificationChannelGroups().get(0);
-        assertThat(group.mId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-        assertThat(group.mNameResId,
-                is(org.chromium.chrome.R.string.notification_category_group_general));
-    }
-
-    @Test
-    public void testEnsureInitialized_sitesChannel() throws Exception {
-        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_SITES);
-
-        assertThat(mMockNotificationManager.getChannels().size(), is(1));
-
-        ChannelDefinitions.Channel channel = mMockNotificationManager.getChannels().get(0);
-        assertThat(channel.mId, is(ChannelDefinitions.CHANNEL_ID_SITES));
-        assertThat(
-                channel.mNameResId, is(org.chromium.chrome.R.string.notification_category_sites));
-        assertThat(channel.mImportance, is(NotificationManager.IMPORTANCE_DEFAULT));
-        assertThat(channel.mGroupId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-
-        assertThat(mMockNotificationManager.getNotificationChannelGroups().size(), is(1));
-        ChannelDefinitions.ChannelGroup group =
-                mMockNotificationManager.getNotificationChannelGroups().get(0);
-        assertThat(group.mId, is(ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-        assertThat(group.mNameResId,
-                is(org.chromium.chrome.R.string.notification_category_group_general));
-    }
-
-    @Test
-    public void testEnsureInitialized_multipleCalls() throws Exception {
-        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_SITES);
-        mChannelsInitializer.ensureInitialized(ChannelDefinitions.CHANNEL_ID_BROWSER);
-        assertThat(mMockNotificationManager.getChannels().size(), is(2));
-    }
-}
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java
index 43f7050..f381e470 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java
@@ -7,9 +7,14 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.app.NotificationManager;
+import android.content.res.Resources;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -17,22 +22,32 @@
 import org.junit.runners.BlockJUnit4ClassRunner;
 
 import org.chromium.base.test.util.InMemorySharedPreferences;
+import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
 import org.chromium.chrome.test.util.browser.notifications.MockNotificationManagerProxy;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Tests that ChannelsUpdater correctly initializes channels on the notification manager.
  */
 @RunWith(BlockJUnit4ClassRunner.class)
 public class ChannelsUpdaterTest {
-    private MockNotificationManagerProxy mMockNotificationManager;
+    private NotificationManagerProxy mMockNotificationManager;
     private InMemorySharedPreferences mMockSharedPreferences;
     private ChannelsInitializer mChannelsInitializer;
+    private Resources mMockResources;
 
     @Before
     public void setUp() throws Exception {
         mMockNotificationManager = new MockNotificationManagerProxy();
-        mChannelsInitializer =
-                new ChannelsInitializer(mMockNotificationManager, new ChannelDefinitions());
+
+        // Mock the resources to prevent nullpointers on string resource lookups (none of these
+        // tests need the real strings, if they did this would need to be an instrumentation test).
+        mMockResources = mock(Resources.class);
+        when(mMockResources.getString(any(Integer.class))).thenReturn("");
+
+        mChannelsInitializer = new ChannelsInitializer(mMockNotificationManager, mMockResources);
         mMockSharedPreferences = new InMemorySharedPreferences();
     }
 
@@ -73,7 +88,7 @@
                 false /* isAtLeastO */, mMockSharedPreferences, mChannelsInitializer, 21);
         updater.updateChannels();
 
-        assertThat(mMockNotificationManager.getChannels().size(), is(0));
+        assertThat(mMockNotificationManager.getNotificationChannels(), hasSize(0));
         assertThat(mMockSharedPreferences.getInt(ChannelsUpdater.CHANNELS_VERSION_KEY, -1), is(-1));
     }
 
@@ -83,8 +98,8 @@
                 true /* isAtLeastO */, mMockSharedPreferences, mChannelsInitializer, 21);
         updater.updateChannels();
 
-        assertThat(mMockNotificationManager.getChannels().size(), is(greaterThan(0)));
-        assertThat(mMockNotificationManager.getNotificationChannelIds(),
+        assertThat(mMockNotificationManager.getNotificationChannels(), hasSize((greaterThan(0))));
+        assertThat(getChannelIds(mMockNotificationManager.getNotificationChannels()),
                 containsInAnyOrder(ChannelDefinitions.CHANNEL_ID_BROWSER,
                         ChannelDefinitions.CHANNEL_ID_DOWNLOADS,
                         ChannelDefinitions.CHANNEL_ID_INCOGNITO,
@@ -92,34 +107,31 @@
         assertThat(mMockSharedPreferences.getInt(ChannelsUpdater.CHANNELS_VERSION_KEY, -1), is(21));
     }
 
-    // Warnings suppressed in order to construct the legacy channels with invalid channel ids.
-    @SuppressWarnings("WrongConstant")
     @Test
     public void testUpdateChannels_deletesLegacyChannelsAndCreatesExpectedOnes() throws Exception {
-        // Fake some legacy channels (since we don't have any yet).
-        ChannelDefinitions channelDefinitions = new ChannelDefinitions() {
-            @Override
-            public String[] getLegacyChannelIds() {
-                return new String[] {"OldChannel", "AnotherOldChannel"};
-            }
-        };
-        mMockNotificationManager.createNotificationChannel(new ChannelDefinitions.Channel(
-                "OldChannel", 8292304, NotificationManager.IMPORTANCE_HIGH,
-                ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
-        mMockNotificationManager.createNotificationChannel(
-                new ChannelDefinitions.Channel("AnotherOldChannel", 8292304,
-                        NotificationManager.IMPORTANCE_LOW, "OldChannelGroup"));
-        assertThat(mMockNotificationManager.getNotificationChannelIds(),
-                containsInAnyOrder("OldChannel", "AnotherOldChannel"));
+        // Set up any legacy channels.
+        for (String id : ChannelDefinitions.getLegacyChannelIds()) {
+            mMockNotificationManager.createNotificationChannel(
+                    new Channel(id, id, NotificationManager.IMPORTANCE_LOW,
+                            ChannelDefinitions.CHANNEL_GROUP_ID_GENERAL));
+        }
 
         ChannelsUpdater updater = new ChannelsUpdater(true /* isAtLeastO */, mMockSharedPreferences,
-                new ChannelsInitializer(mMockNotificationManager, channelDefinitions), 12);
+                new ChannelsInitializer(mMockNotificationManager, mMockResources), 12);
         updater.updateChannels();
 
-        assertThat(mMockNotificationManager.getNotificationChannelIds(),
+        assertThat(getChannelIds(mMockNotificationManager.getNotificationChannels()),
                 containsInAnyOrder(ChannelDefinitions.CHANNEL_ID_BROWSER,
                         ChannelDefinitions.CHANNEL_ID_DOWNLOADS,
                         ChannelDefinitions.CHANNEL_ID_INCOGNITO,
                         ChannelDefinitions.CHANNEL_ID_SITES, ChannelDefinitions.CHANNEL_ID_MEDIA));
     }
+
+    private static List<String> getChannelIds(List<Channel> channels) {
+        List<String> ids = new ArrayList<>();
+        for (Channel ch : channels) {
+            ids.add(ch.getId());
+        }
+        return ids;
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
index a4cfab3..3fede53 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
@@ -16,7 +16,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.MockitoAnnotations;
@@ -71,6 +70,9 @@
     ArgumentCaptor<String[]> mIdsArgument;
 
     @Captor
+    ArgumentCaptor<long[]> mOfflineIdsArgument;
+
+    @Captor
     ArgumentCaptor<Callback<Integer>> mDeleteCallbackArgument;
 
     /**
@@ -225,6 +227,51 @@
         verify(callback, times(1)).onResult(any(Integer.class));
     }
 
+    @Test
+    @Feature({"OfflinePages"})
+    public void testDeletePagesByOfflineIds_listOfOfflineIdsNull() {
+        // -1 means to check for null in the Answer.
+        final int itemCount = -1;
+
+        answerDeletePagesByOfflineIds(itemCount);
+        Callback<Integer> callback = createDeletePageCallback();
+        List<Long> list = null;
+
+        mBridge.deletePagesByOfflineId(list, callback);
+
+        verify(callback, times(1)).onResult(any(Integer.class));
+    }
+
+    @Test
+    @Feature({"OfflinePages"})
+    public void testDeletePagesByOfflineIds_listOfOfflineIdsEmpty() {
+        final int itemCount = 0;
+
+        answerDeletePagesByOfflineIds(itemCount);
+        Callback<Integer> callback = createDeletePageCallback();
+        List<Long> list = new ArrayList<>();
+
+        mBridge.deletePagesByOfflineId(list, callback);
+
+        verify(callback, times(1)).onResult(any(Integer.class));
+    }
+
+    @Test
+    @Feature({"OfflinePages"})
+    public void testDeletePagesByOfflineIds() {
+        final int itemCount = 2;
+
+        answerDeletePagesByOfflineIds(itemCount);
+        Callback<Integer> callback = createDeletePageCallback();
+        List<Long> list = new ArrayList<>();
+        list.add(Long.valueOf(1));
+        list.add(Long.valueOf(2));
+
+        mBridge.deletePagesByOfflineId(list, callback);
+
+        verify(callback, times(1)).onResult(any(Integer.class));
+    }
+
     /** Performs a proper cast from Object to a List<OfflinePageItem>. */
     private static List<OfflinePageItem> convertToListOfOfflinePages(Object o) {
         @SuppressWarnings("unchecked")
@@ -293,6 +340,27 @@
                 mCallbackArgument.capture());
     }
 
+    private void answerDeletePagesByOfflineIds(final int itemCount) {
+        Answer<Void> answer = new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                long[] offlineIds = mOfflineIdsArgument.getValue();
+
+                if (itemCount < 0) {
+                    assertEquals(offlineIds, null);
+                } else {
+                    assertEquals(offlineIds.length, itemCount);
+                }
+                mDeleteCallbackArgument.getValue().onResult(Integer.valueOf(0));
+
+                return null;
+            }
+        };
+
+        doAnswer(answer).when(mBridge).nativeDeletePagesByOfflineId(
+                anyLong(), mOfflineIdsArgument.capture(), mDeleteCallbackArgument.capture());
+    }
+
     private void answerDeletePagesByClientIds(final int itemCount) {
         Answer<Void> answer = new Answer<Void>() {
             @Override
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 7b3187b8..cf3bf21c 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1804,8 +1804,8 @@
       "renderer_context_menu/context_menu_content_type_platform_app.h",
       "renderer_host/chrome_extension_message_filter.cc",
       "renderer_host/chrome_extension_message_filter.h",
-      "safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.cc",
-      "safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.h",
+      "safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.cc",
+      "safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.h",
       "safe_browsing/settings_reset_prompt/default_settings_fetcher.cc",
       "safe_browsing/settings_reset_prompt/default_settings_fetcher.h",
       "safe_browsing/settings_reset_prompt/extension_info.cc",
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 62038f2c..3b545b33 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -59,6 +59,7 @@
     &kContextualSuggestionsCarousel,
     &kCustomContextMenu,
     &kCustomFeedbackUi,
+    &kDownloadHomeShowStorageInfo,
     &data_reduction_proxy::features::kDataReductionMainMenu,
     &data_reduction_proxy::features::kDataReductionSiteBreakdown,
     &kFullscreenActivity,
@@ -155,6 +156,9 @@
 const base::Feature kDownloadAutoResumptionThrottling{
     "DownloadAutoResumptionThrottling", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kDownloadHomeShowStorageInfo{
+    "DownloadHomeShowStorageInfo", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kFullscreenActivity{"FullscreenActivity",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index 81c6071..6fa070e 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -29,6 +29,7 @@
 extern const base::Feature kCustomContextMenu;
 extern const base::Feature kCustomFeedbackUi;
 extern const base::Feature kDownloadAutoResumptionThrottling;
+extern const base::Feature kDownloadHomeShowStorageInfo;
 extern const base::Feature kFullscreenActivity;
 extern const base::Feature kImportantSitesInCBD;
 extern const base::Feature kImprovedA2HS;
diff --git a/chrome/browser/android/offline_pages/offline_page_bridge.cc b/chrome/browser/android/offline_pages/offline_page_bridge.cc
index b93155a..7a54ab1 100644
--- a/chrome/browser/android/offline_pages/offline_page_bridge.cc
+++ b/chrome/browser/android/offline_pages/offline_page_bridge.cc
@@ -385,6 +385,20 @@
       client_ids, base::Bind(&DeletePageCallback, j_callback_ref));
 }
 
+void OfflinePageBridge::DeletePagesByOfflineId(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jlongArray>& j_offline_ids_array,
+    const JavaParamRef<jobject>& j_callback_obj) {
+  ScopedJavaGlobalRef<jobject> j_callback_ref;
+  j_callback_ref.Reset(env, j_callback_obj);
+  std::vector<int64_t> offline_ids;
+  base::android::JavaLongArrayToInt64Vector(env, j_offline_ids_array,
+                                            &offline_ids);
+  offline_page_model_->DeletePagesByOfflineId(
+      offline_ids, base::Bind(&DeletePageCallback, j_callback_ref));
+}
+
 void OfflinePageBridge::GetPagesByClientId(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
@@ -403,6 +417,27 @@
       client_ids, base::Bind(&MultipleOfflinePageItemCallback, j_result_ref,
                              j_callback_ref));
 }
+void OfflinePageBridge::GetPagesForNamespace(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jobject>& j_result_obj,
+    const JavaParamRef<jstring>& j_namespace,
+    const JavaParamRef<jobject>& j_callback_obj) {
+  ScopedJavaGlobalRef<jobject> j_result_ref(env, j_result_obj);
+
+  ScopedJavaGlobalRef<jobject> j_callback_ref;
+  j_callback_ref.Reset(env, j_callback_obj);
+
+  std::string name_space = ConvertJavaStringToUTF8(env, j_namespace);
+
+  OfflinePageModelQueryBuilder builder;
+  builder.RequireNamespace(name_space);
+
+  offline_page_model_->GetPagesMatchingQuery(
+      builder.Build(offline_page_model_->GetPolicyController()),
+      base::Bind(&MultipleOfflinePageItemCallback, j_result_ref,
+                 j_callback_ref));
+}
 
 void OfflinePageBridge::SelectPageForOnlineUrl(
     JNIEnv* env,
diff --git a/chrome/browser/android/offline_pages/offline_page_bridge.h b/chrome/browser/android/offline_pages/offline_page_bridge.h
index db898dfb..49b8d5a 100644
--- a/chrome/browser/android/offline_pages/offline_page_bridge.h
+++ b/chrome/browser/android/offline_pages/offline_page_bridge.h
@@ -69,6 +69,12 @@
       const base::android::JavaParamRef<jobjectArray>& j_ids_array,
       const base::android::JavaParamRef<jobject>& j_callback_obj);
 
+  void DeletePagesByOfflineId(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      const base::android::JavaParamRef<jlongArray>& j_offline_ids_array,
+      const base::android::JavaParamRef<jobject>& j_callback_obj);
+
   void GetPagesByClientId(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
@@ -77,6 +83,13 @@
       const base::android::JavaParamRef<jobjectArray>& j_ids_array,
       const base::android::JavaParamRef<jobject>& j_callback_obj);
 
+  void GetPagesForNamespace(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      const base::android::JavaParamRef<jobject>& j_result_obj,
+      const base::android::JavaParamRef<jstring>& j_namespace,
+      const base::android::JavaParamRef<jobject>& j_callback_obj);
+
   void SelectPageForOnlineUrl(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/android/preferences/pref_service_bridge.cc b/chrome/browser/android/preferences/pref_service_bridge.cc
index b60b402..ab47a4f 100644
--- a/chrome/browser/android/preferences/pref_service_bridge.cc
+++ b/chrome/browser/android/preferences/pref_service_bridge.cc
@@ -43,7 +43,7 @@
 #include "components/metrics/metrics_pref_names.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/signin/core/common/signin_pref_names.h"
 #include "components/strings/grit/components_locale_settings.h"
 #include "components/translate/core/browser/translate_pref_names.h"
diff --git a/chrome/browser/android/vr_shell/BUILD.gn b/chrome/browser/android/vr_shell/BUILD.gn
index 404275f..45afdcf 100644
--- a/chrome/browser/android/vr_shell/BUILD.gn
+++ b/chrome/browser/android/vr_shell/BUILD.gn
@@ -42,10 +42,14 @@
       "textures/insecure_content_transient_texture.h",
       "textures/loading_indicator_texture.cc",
       "textures/loading_indicator_texture.h",
+      "textures/system_indicator_texture.cc",
+      "textures/system_indicator_texture.h",
       "textures/ui_texture.cc",
       "textures/ui_texture.h",
       "textures/url_bar_texture.cc",
       "textures/url_bar_texture.h",
+      "ui_elements/audio_capture_indicator.cc",
+      "ui_elements/audio_capture_indicator.h",
       "ui_elements/close_button.cc",
       "ui_elements/close_button.h",
       "ui_elements/loading_indicator.cc",
@@ -60,6 +64,8 @@
       "ui_elements/ui_element.h",
       "ui_elements/url_bar.cc",
       "ui_elements/url_bar.h",
+      "ui_elements/video_capture_indicator.cc",
+      "ui_elements/video_capture_indicator.h",
       "ui_interface.h",
       "ui_scene.cc",
       "ui_scene.h",
diff --git a/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.h b/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.h
index 1fbc1b52..ae48322 100644
--- a/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.h
+++ b/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_INSECURE_CONTENT_PERMANENT_TEXTURE_H_
 #define CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_INSECURE_CONTENT_PERMANENT_TEXTURE_H_
 
+#include "base/macros.h"
 #include "chrome/browser/android/vr_shell/textures/ui_texture.h"
 
 namespace vr_shell {
@@ -20,6 +21,8 @@
   void Draw(SkCanvas* canvas, const gfx::Size& texture_size) override;
 
   gfx::SizeF size_;
+
+  DISALLOW_COPY_AND_ASSIGN(InsecureContentPermanentTexture);
 };
 
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/textures/insecure_content_transient_texture.h b/chrome/browser/android/vr_shell/textures/insecure_content_transient_texture.h
index 8a11938b..8cabcab 100644
--- a/chrome/browser/android/vr_shell/textures/insecure_content_transient_texture.h
+++ b/chrome/browser/android/vr_shell/textures/insecure_content_transient_texture.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_INSECURE_CONTENT_TRANSIENT_TEXTURE_H_
 #define CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_INSECURE_CONTENT_TRANSIENT_TEXTURE_H_
 
+#include "base/macros.h"
 #include "chrome/browser/android/vr_shell/textures/ui_texture.h"
 
 namespace vr_shell {
@@ -20,6 +21,8 @@
   void Draw(SkCanvas* sk_canvas, const gfx::Size& texture_size) override;
 
   gfx::SizeF size_;
+
+  DISALLOW_COPY_AND_ASSIGN(InsecureContentTransientTexture);
 };
 
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/textures/system_indicator_texture.cc b/chrome/browser/android/vr_shell/textures/system_indicator_texture.cc
new file mode 100644
index 0000000..69d24dd
--- /dev/null
+++ b/chrome/browser/android/vr_shell/textures/system_indicator_texture.cc
@@ -0,0 +1,101 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/vr_shell/textures/system_indicator_texture.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "cc/paint/skia_paint_canvas.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font_list.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/gfx/render_text.h"
+#include "ui/gfx/vector_icon_types.h"
+
+namespace vr_shell {
+
+namespace {
+
+const SkColor kBackgroundColor = SK_ColorWHITE;
+const SkColor kForegroundColor = 0xFF444444;
+constexpr float kBorderFactor = 0.1;
+constexpr float kIconSizeFactor = 0.7;
+constexpr float kFontSizeFactor = 0.40;
+constexpr float kTextHeightFactor = 1.0 - 2 * kBorderFactor;
+constexpr float kTextWidthFactor = 4.0 - 3 * kBorderFactor - kIconSizeFactor;
+
+}  // namespace
+
+SystemIndicatorTexture::SystemIndicatorTexture(const gfx::VectorIcon& icon,
+                                               int message_id)
+    : icon_(icon), message_id_(message_id) {}
+
+SystemIndicatorTexture::~SystemIndicatorTexture() = default;
+
+void SystemIndicatorTexture::Draw(SkCanvas* sk_canvas,
+                                  const gfx::Size& texture_size) {
+  cc::SkiaPaintCanvas paint_canvas(sk_canvas);
+  gfx::Canvas gfx_canvas(&paint_canvas, 1.0f);
+  gfx::Canvas* canvas = &gfx_canvas;
+
+  DCHECK(texture_size.height() * 4 == texture_size.width());
+  size_.set_height(texture_size.height());
+  SkPaint paint;
+  paint.setColor(kBackgroundColor);
+
+  base::string16 text;
+
+  // TODO(acondor): Set proper strings in resources files.
+  if (message_id_)
+    text = l10n_util::GetStringUTF16(message_id_);
+  else
+    text = base::UTF8ToUTF16("<message>");
+
+  auto fonts = GetFontList(size_.height() * kFontSizeFactor, text);
+  gfx::Rect text_size(0, kTextHeightFactor * size_.height());
+
+  std::vector<std::unique_ptr<gfx::RenderText>> lines =
+      PrepareDrawStringRect(text, fonts, kForegroundColor, &text_size, 0);
+
+  DCHECK_LE(text_size.width(), kTextWidthFactor * size_.height());
+  // Setting background size giving some extra lateral padding to the text.
+  size_.set_width((5 * kBorderFactor + kIconSizeFactor) * size_.height() +
+                  text_size.width());
+  float radius = size_.height() * kBorderFactor;
+  sk_canvas->drawRoundRect(SkRect::MakeWH(size_.width(), size_.height()),
+                           radius, radius, paint);
+
+  canvas->Save();
+  canvas->Translate(gfx::Vector2d(
+      IsRTL() ? 4 * kBorderFactor * size_.height() + text_size.width()
+              : size_.height() * kBorderFactor,
+      size_.height() * (1.0 - kIconSizeFactor) / 2.0));
+  PaintVectorIcon(canvas, icon_, size_.height() * kIconSizeFactor,
+                  kForegroundColor);
+  canvas->Restore();
+
+  canvas->Save();
+  canvas->Translate(gfx::Vector2d(
+      size_.height() *
+          (IsRTL() ? 2 * kBorderFactor : 3 * kBorderFactor + kIconSizeFactor),
+      size_.height() * kBorderFactor));
+  for (auto& render_text : lines)
+    render_text->Draw(canvas);
+  canvas->Restore();
+}
+
+gfx::Size SystemIndicatorTexture::GetPreferredTextureSize(
+    int maximum_width) const {
+  // Ensuring height is a quarter of the width.
+  int height = maximum_width / 4;
+  return gfx::Size(height * 4, height);
+}
+
+gfx::SizeF SystemIndicatorTexture::GetDrawnSize() const {
+  return size_;
+}
+
+}  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/textures/system_indicator_texture.h b/chrome/browser/android/vr_shell/textures/system_indicator_texture.h
new file mode 100644
index 0000000..b20ef997
--- /dev/null
+++ b/chrome/browser/android/vr_shell/textures/system_indicator_texture.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 CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_SYSTEM_INDICATOR_TEXTURE_H_
+#define CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_SYSTEM_INDICATOR_TEXTURE_H_
+
+#include "base/macros.h"
+#include "chrome/browser/android/vr_shell/textures/ui_texture.h"
+#include "ui/gfx/vector_icon_types.h"
+
+namespace vr_shell {
+
+class SystemIndicatorTexture : public UiTexture {
+ public:
+  SystemIndicatorTexture(const gfx::VectorIcon& icon, int message_id);
+  ~SystemIndicatorTexture() override;
+  gfx::Size GetPreferredTextureSize(int width) const override;
+  gfx::SizeF GetDrawnSize() const override;
+
+ private:
+  void Draw(SkCanvas* canvas, const gfx::Size& texture_size) override;
+
+  gfx::SizeF size_;
+  const gfx::VectorIcon& icon_;
+  int message_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(SystemIndicatorTexture);
+};
+
+}  // namespace vr_shell
+
+#endif  // CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_SYSTEM_INDICATOR_TEXTURE_H_
diff --git a/chrome/browser/android/vr_shell/textures/ui_texture.h b/chrome/browser/android/vr_shell/textures/ui_texture.h
index 5154cd72..e0d3b3ec 100644
--- a/chrome/browser/android/vr_shell/textures/ui_texture.h
+++ b/chrome/browser/android/vr_shell/textures/ui_texture.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/macros.h"
 #include "base/strings/string16.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect.h"
@@ -71,6 +72,8 @@
  private:
   int draw_flags_ = 0;
   bool dirty_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(UiTexture);
 };
 
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/textures/url_bar_texture.h b/chrome/browser/android/vr_shell/textures/url_bar_texture.h
index c85e806..cf0a6ab2 100644
--- a/chrome/browser/android/vr_shell/textures/url_bar_texture.h
+++ b/chrome/browser/android/vr_shell/textures/url_bar_texture.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/macros.h"
 #include "chrome/browser/android/vr_shell/textures/ui_texture.h"
 #include "url/gurl.h"
 
@@ -41,6 +42,8 @@
   GURL gurl_;
   GURL last_drawn_gurl_;
   std::vector<std::unique_ptr<gfx::RenderText>> gurl_render_texts_;
+
+  DISALLOW_COPY_AND_ASSIGN(UrlBarTexture);
 };
 
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/ui_elements/audio_capture_indicator.cc b/chrome/browser/android/vr_shell/ui_elements/audio_capture_indicator.cc
new file mode 100644
index 0000000..843383e4
--- /dev/null
+++ b/chrome/browser/android/vr_shell/ui_elements/audio_capture_indicator.cc
@@ -0,0 +1,26 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/vr_shell/ui_elements/audio_capture_indicator.h"
+
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/android/vr_shell/textures/system_indicator_texture.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/vector_icons/vector_icons.h"
+
+namespace vr_shell {
+
+// TODO(acondor): Set a proper string.
+AudioCaptureIndicator::AudioCaptureIndicator(int preferred_width)
+    : TexturedElement(preferred_width),
+      texture_(
+          base::MakeUnique<SystemIndicatorTexture>(ui::kMicrophoneIcon, 0)) {}
+
+AudioCaptureIndicator::~AudioCaptureIndicator() = default;
+
+UiTexture* AudioCaptureIndicator::GetTexture() const {
+  return texture_.get();
+}
+
+}  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/ui_elements/audio_capture_indicator.h b/chrome/browser/android/vr_shell/ui_elements/audio_capture_indicator.h
new file mode 100644
index 0000000..c400e58
--- /dev/null
+++ b/chrome/browser/android/vr_shell/ui_elements/audio_capture_indicator.h
@@ -0,0 +1,31 @@
+// 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 CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENTS_AUDIO_CAPTURE_INDICATOR_H_
+#define CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENTS_AUDIO_CAPTURE_INDICATOR_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/android/vr_shell/ui_elements/textured_element.h"
+
+namespace vr_shell {
+
+class UiTexture;
+
+class AudioCaptureIndicator : public TexturedElement {
+ public:
+  explicit AudioCaptureIndicator(int preferred_width);
+  ~AudioCaptureIndicator() override;
+
+ private:
+  UiTexture* GetTexture() const override;
+  std::unique_ptr<UiTexture> texture_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioCaptureIndicator);
+};
+
+}  // namespace vr_shell
+
+#endif  // CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENTS_AUDIO_CAPTURE_INDICATOR_H_
diff --git a/chrome/browser/android/vr_shell/ui_elements/video_capture_indicator.cc b/chrome/browser/android/vr_shell/ui_elements/video_capture_indicator.cc
new file mode 100644
index 0000000..5ad04a8c
--- /dev/null
+++ b/chrome/browser/android/vr_shell/ui_elements/video_capture_indicator.cc
@@ -0,0 +1,26 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/vr_shell/ui_elements/video_capture_indicator.h"
+
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/android/vr_shell/textures/system_indicator_texture.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/vector_icons/vector_icons.h"
+
+namespace vr_shell {
+
+// TODO(acondor): Set a proper string.
+VideoCaptureIndicator::VideoCaptureIndicator(int preferred_width)
+    : TexturedElement(preferred_width),
+      texture_(base::MakeUnique<SystemIndicatorTexture>(ui::kVideocamIcon, 0)) {
+}
+
+VideoCaptureIndicator::~VideoCaptureIndicator() = default;
+
+UiTexture* VideoCaptureIndicator::GetTexture() const {
+  return texture_.get();
+}
+
+}  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/ui_elements/video_capture_indicator.h b/chrome/browser/android/vr_shell/ui_elements/video_capture_indicator.h
new file mode 100644
index 0000000..8b39a407
--- /dev/null
+++ b/chrome/browser/android/vr_shell/ui_elements/video_capture_indicator.h
@@ -0,0 +1,31 @@
+// 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 CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENTS_VIDEO_CAPTURE_INDICATOR_H_
+#define CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENTS_VIDEO_CAPTURE_INDICATOR_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/android/vr_shell/ui_elements/textured_element.h"
+
+namespace vr_shell {
+
+class UiTexture;
+
+class VideoCaptureIndicator : public TexturedElement {
+ public:
+  explicit VideoCaptureIndicator(int preferred_width);
+  ~VideoCaptureIndicator() override;
+
+ private:
+  UiTexture* GetTexture() const override;
+  std::unique_ptr<UiTexture> texture_;
+
+  DISALLOW_COPY_AND_ASSIGN(VideoCaptureIndicator);
+};
+
+}  // namespace vr_shell
+
+#endif  // CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENTS_VIDEO_CAPTURE_INDICATOR_H_
diff --git a/chrome/browser/android/vr_shell/ui_scene_manager.cc b/chrome/browser/android/vr_shell/ui_scene_manager.cc
index 378aa7d8..0747344 100644
--- a/chrome/browser/android/vr_shell/ui_scene_manager.cc
+++ b/chrome/browser/android/vr_shell/ui_scene_manager.cc
@@ -7,12 +7,14 @@
 #include "base/callback.h"
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/android/vr_shell/textures/ui_texture.h"
+#include "chrome/browser/android/vr_shell/ui_elements/audio_capture_indicator.h"
 #include "chrome/browser/android/vr_shell/ui_elements/close_button.h"
 #include "chrome/browser/android/vr_shell/ui_elements/loading_indicator.h"
 #include "chrome/browser/android/vr_shell/ui_elements/permanent_security_warning.h"
 #include "chrome/browser/android/vr_shell/ui_elements/transient_security_warning.h"
 #include "chrome/browser/android/vr_shell/ui_elements/ui_element.h"
 #include "chrome/browser/android/vr_shell/ui_elements/url_bar.h"
+#include "chrome/browser/android/vr_shell/ui_elements/video_capture_indicator.h"
 #include "chrome/browser/android/vr_shell/ui_scene.h"
 #include "chrome/browser/android/vr_shell/vr_browser_interface.h"
 #include "chrome/browser/android/vr_shell/vr_shell.h"
@@ -65,6 +67,7 @@
   CreateBackground();
   CreateContentQuad();
   CreateSecurityWarnings();
+  CreateSystemIndicators();
   CreateUrlBar();
   if (in_cct_)
     CreateCloseButton();
@@ -107,6 +110,28 @@
   scene_->AddUiElement(std::move(element));
 }
 
+void UiSceneManager::CreateSystemIndicators() {
+  std::unique_ptr<UiElement> element;
+
+  // TODO(acondor): Make constants for sizes and positions once the UX for the
+  // indicators is defined.
+  element = base::MakeUnique<AudioCaptureIndicator>(256);
+  element->set_id(AllocateId());
+  element->set_translation({-0.3, 0.8, -1.9});
+  element->set_size({0.4, 0, 1});
+  element->set_visible(false);
+  audio_input_indicator_ = element.get();
+  scene_->AddUiElement(std::move(element));
+
+  element = base::MakeUnique<VideoCaptureIndicator>(256);
+  element->set_id(AllocateId());
+  element->set_translation({0.3, 0.8, -1.9});
+  element->set_size({0.4, 0, 1});
+  element->set_visible(false);
+  video_input_indicator_ = element.get();
+  scene_->AddUiElement(std::move(element));
+}
+
 void UiSceneManager::CreateContentQuad() {
   std::unique_ptr<UiElement> element;
 
diff --git a/chrome/browser/android/vr_shell/ui_scene_manager.h b/chrome/browser/android/vr_shell/ui_scene_manager.h
index 81e7f5e5..ef917ad5 100644
--- a/chrome/browser/android/vr_shell/ui_scene_manager.h
+++ b/chrome/browser/android/vr_shell/ui_scene_manager.h
@@ -46,6 +46,7 @@
 
  private:
   void CreateSecurityWarnings();
+  void CreateSystemIndicators();
   void CreateContentQuad();
   void CreateBackground();
   void CreateUrlBar();
@@ -65,6 +66,8 @@
   UiElement* permanent_security_warning_ = nullptr;
   UiElement* transient_security_warning_ = nullptr;
   UiElement* main_content_ = nullptr;
+  UiElement* audio_input_indicator_ = nullptr;
+  UiElement* video_input_indicator_ = nullptr;
   UrlBar* url_bar_ = nullptr;
   LoadingIndicator* loading_indicator_ = nullptr;
 
diff --git a/chrome/browser/apps/drive/drive_service_bridge.cc b/chrome/browser/apps/drive/drive_service_bridge.cc
index 648aa2933..484a54a62 100644
--- a/chrome/browser/apps/drive/drive_service_bridge.cc
+++ b/chrome/browser/apps/drive/drive_service_bridge.cc
@@ -9,7 +9,7 @@
 
 #include "base/logging.h"
 #include "base/macros.h"
-#include "base/threading/sequenced_worker_pool.h"
+#include "base/task_scheduler/post_task.h"
 #include "chrome/browser/drive/drive_notification_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
@@ -20,7 +20,6 @@
 #include "components/drive/service/drive_api_service.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_manager.h"
-#include "content/public/browser/browser_thread.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 
 namespace {
@@ -72,12 +71,10 @@
 }
 
 void DriveServiceBridgeImpl::Initialize() {
-  scoped_refptr<base::SequencedWorkerPool> worker_pool(
-      content::BrowserThread::GetBlockingPool());
   scoped_refptr<base::SequencedTaskRunner> drive_task_runner(
-      worker_pool->GetSequencedTaskRunnerWithShutdownBehavior(
-          worker_pool->GetSequenceToken(),
-          base::SequencedWorkerPool::SKIP_ON_SHUTDOWN));
+      base::CreateSequencedTaskRunnerWithTraits(
+          {base::MayBlock(), base::TaskPriority::BACKGROUND,
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
 
   ProfileOAuth2TokenService* token_service =
       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
diff --git a/chrome/browser/autofill/validation_rules_storage_factory.cc b/chrome/browser/autofill/validation_rules_storage_factory.cc
index 8265c58..6c6d741 100644
--- a/chrome/browser/autofill/validation_rules_storage_factory.cc
+++ b/chrome/browser/autofill/validation_rules_storage_factory.cc
@@ -6,11 +6,9 @@
 
 #include "base/files/file_path.h"
 #include "base/path_service.h"
-#include "base/threading/sequenced_worker_pool.h"
 #include "chrome/common/chrome_paths.h"
 #include "components/prefs/json_pref_store.h"
 #include "components/prefs/pref_filter.h"
-#include "content/public/browser/browser_thread.h"
 #include "third_party/libaddressinput/chromium/chrome_storage_impl.h"
 
 namespace autofill {
@@ -30,15 +28,8 @@
   bool success = PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
   DCHECK(success);
 
-  base::FilePath cache =
-      user_data_dir.Append(FILE_PATH_LITERAL("Address Validation Rules"));
-
-  scoped_refptr<base::SequencedTaskRunner> task_runner =
-      JsonPrefStore::GetTaskRunnerForFile(
-          cache, content::BrowserThread::GetBlockingPool());
-
-  json_pref_store_ = new JsonPrefStore(cache, task_runner.get(),
-                                       std::unique_ptr<PrefFilter>());
+  json_pref_store_ = new JsonPrefStore(
+      user_data_dir.Append(FILE_PATH_LITERAL("Address Validation Rules")));
   json_pref_store_->ReadPrefsAsync(NULL);
 }
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 00d589b5..0738496b 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -143,7 +143,7 @@
 #include "components/rappor/public/rappor_utils.h"
 #include "components/rappor/rappor_recorder_impl.h"
 #include "components/rappor/rappor_service_impl.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/security_interstitials/core/ssl_error_ui.h"
 #include "components/signin/core/common/profile_management_switches.h"
 #include "components/spellcheck/spellcheck_build_features.h"
diff --git a/chrome/browser/chromeos/accessibility/event_handler_common.cc b/chrome/browser/chromeos/accessibility/event_handler_common.cc
index d1da17a1..efadb10b 100644
--- a/chrome/browser/chromeos/accessibility/event_handler_common.cc
+++ b/chrome/browser/chromeos/accessibility/event_handler_common.cc
@@ -49,6 +49,7 @@
   }
 
   const content::NativeWebKeyboardEvent web_event(key_event);
+  // Don't forward latency info, as these are getting forwarded to an extension.
   rvh->GetWidget()->ForwardKeyboardEvent(web_event);
 }
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/arc/process/arc_process.cc b/chrome/browser/chromeos/arc/process/arc_process.cc
index 6bafc72..fbb8d7c 100644
--- a/chrome/browser/chromeos/arc/process/arc_process.cc
+++ b/chrome/browser/chromeos/arc/process/arc_process.cc
@@ -8,15 +8,6 @@
 
 namespace arc {
 
-namespace {
-
-// A special process on Android side which serves as a dummy "focused" app
-// when the focused window is a Chrome side window (i.e., all Android
-// processes are running in the background). We don't want to kill it anyway.
-constexpr char kArcHomeProcess[] = "org.chromium.arc.home";
-
-}  // namespace
-
 ArcProcess::ArcProcess(base::ProcessId nspid,
                        base::ProcessId pid,
                        const std::string& process_name,
@@ -44,16 +35,14 @@
 ArcProcess& ArcProcess::operator=(ArcProcess&& other) = default;
 
 bool ArcProcess::IsImportant() const {
-  return process_state() <= mojom::ProcessState::IMPORTANT_FOREGROUND ||
-         process_name() == kArcHomeProcess;
+  return process_state() <= mojom::ProcessState::IMPORTANT_FOREGROUND;
 }
 
 bool ArcProcess::IsKernelKillable() const {
   // Protect PERSISTENT, PERSISTENT_UI, and our HOME processes since they should
   // never be killed even by the kernel. Returning false for them allows their
   // OOM adjustment scores to remain negative.
-  return process_state() > arc::mojom::ProcessState::PERSISTENT_UI &&
-         process_name() != kArcHomeProcess;
+  return process_state() > arc::mojom::ProcessState::PERSISTENT_UI;
 }
 
 }  // namespace arc
diff --git a/chrome/browser/chromeos/arc/process/arc_process_unittest.cc b/chrome/browser/chromeos/arc/process/arc_process_unittest.cc
index aea2d2bc..b351dbef5 100644
--- a/chrome/browser/chromeos/arc/process/arc_process_unittest.cc
+++ b/chrome/browser/chromeos/arc/process/arc_process_unittest.cc
@@ -120,15 +120,6 @@
   EXPECT_FALSE(ArcProcess(0, 0, "process", mojom::ProcessState::CACHED_EMPTY,
                           kIsNotFocused, 0)
                    .IsImportant());
-
-  // Exceptions: the function always returns true ignoring ProcessState for our
-  // HOME process.
-  EXPECT_TRUE(ArcProcess(0, 0, "org.chromium.arc.home",
-                         mojom::ProcessState::TOP, kIsNotFocused, 0)
-                  .IsImportant());
-  EXPECT_TRUE(ArcProcess(0, 0, "org.chromium.arc.home",
-                         mojom::ProcessState::HOME, kIsNotFocused, 0)
-                  .IsImportant());
 }
 
 TEST(ArcProcess, TestIsKernelKillable) {
@@ -196,15 +187,6 @@
   EXPECT_TRUE(ArcProcess(0, 0, "process", mojom::ProcessState::CACHED_EMPTY,
                          kIsNotFocused, 0)
                   .IsKernelKillable());
-
-  // Exceptions: the function always returns false ignoring ProcessState for our
-  // HOME process.
-  EXPECT_FALSE(ArcProcess(0, 0, "org.chromium.arc.home",
-                          mojom::ProcessState::TOP, kIsNotFocused, 0)
-                   .IsKernelKillable());
-  EXPECT_FALSE(ArcProcess(0, 0, "org.chromium.arc.home",
-                          mojom::ProcessState::HOME, kIsNotFocused, 0)
-                   .IsKernelKillable());
 }
 
 }  // namespace
diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc
index c627089d..3bdf61d 100644
--- a/chrome/browser/download/download_browsertest.cc
+++ b/chrome/browser/download/download_browsertest.cc
@@ -81,8 +81,8 @@
 #include "components/infobars/core/confirm_infobar_delegate.h"
 #include "components/infobars/core/infobar.h"
 #include "components/prefs/pref_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/csd.pb.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/download_danger_type.h"
 #include "content/public/browser/download_interrupt_reasons.h"
 #include "content/public/browser/download_item.h"
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 7ac9d43..9c7ccd5 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -858,8 +858,8 @@
     "//components/proxy_config",
     "//components/rappor",
     "//components/resources",
+    "//components/safe_browsing/common:safe_browsing_prefs",
     "//components/safe_browsing_db:database_manager",
-    "//components/safe_browsing_db:safe_browsing_prefs",
     "//components/safe_json",
     "//components/search_engines",
     "//components/sessions",
diff --git a/chrome/browser/extensions/api/preference/preference_api.cc b/chrome/browser/extensions/api/preference/preference_api.cc
index 2a4b642..87d9428 100644
--- a/chrome/browser/extensions/api/preference/preference_api.cc
+++ b/chrome/browser/extensions/api/preference/preference_api.cc
@@ -32,7 +32,7 @@
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/proxy_config/proxy_config_pref_names.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/spellcheck/browser/pref_names.h"
 #include "components/translate/core/browser/translate_pref_names.h"
 #include "content/public/browser/notification_details.h"
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 6a274bf..21fbe30 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -19,7 +19,7 @@
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/proxy_config/proxy_config_pref_names.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/search_engines/default_search_manager.h"
 #include "components/spellcheck/browser/pref_names.h"
 #include "components/translate/core/browser/translate_pref_names.h"
diff --git a/chrome/browser/interstitials/chrome_controller_client.cc b/chrome/browser/interstitials/chrome_controller_client.cc
index 5da412d..7c46a06 100644
--- a/chrome/browser/interstitials/chrome_controller_client.cc
+++ b/chrome/browser/interstitials/chrome_controller_client.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
 
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index b92436f..6ef847c 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -108,6 +108,7 @@
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_builder.h"
 #include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_context_storage.h"
 #include "net/url_request/url_request_job_factory_impl.h"
 #include "url/url_constants.h"
 
@@ -197,13 +198,19 @@
 #endif
   }
 
- private:
   ~SystemURLRequestContext() override {
     AssertNoURLRequests();
 #if defined(USE_NSS_CERTS)
     net::SetURLRequestContextForNSSHttpIO(NULL);
 #endif
+
+#if defined(OS_ANDROID)
+    net::CertVerifyProcAndroid::ShutdownCertNetFetcher();
+#endif
   }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SystemURLRequestContext);
 };
 
 std::unique_ptr<net::HostResolver> CreateGlobalHostResolver(
@@ -396,6 +403,8 @@
   pref_proxy_config_tracker_.reset(
       ProxyServiceFactory::CreatePrefProxyConfigTrackerOfLocalState(
           local_state));
+  system_proxy_config_service_ = ProxyServiceFactory::CreateProxyConfigService(
+      pref_proxy_config_tracker_.get());
   ChromeNetworkDelegate::InitializePrefsOnUIThread(
       &system_enable_referrers_,
       nullptr,
@@ -487,7 +496,8 @@
 net::URLRequestContextGetter* IOThread::system_url_request_context_getter() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!system_url_request_context_getter_.get()) {
-    InitSystemRequestContext();
+    system_url_request_context_getter_ =
+        new SystemURLRequestContextGetter(this);
   }
   return system_url_request_context_getter_.get();
 }
@@ -631,14 +641,6 @@
   // For the ProxyScriptFetcher, we use a direct ProxyService.
   globals_->proxy_script_fetcher_proxy_service =
       net::ProxyService::CreateDirectWithNetLog(net_log_);
-  // In-memory cookie store.
-  globals_->system_cookie_store =
-      content::CreateCookieStore(content::CookieStoreConfig());
-  // In-memory channel ID store.
-  globals_->system_channel_id_service.reset(
-      new net::ChannelIDService(new net::DefaultChannelIDStore(NULL)));
-  globals_->system_cookie_store->SetChannelIDServiceID(
-      globals_->system_channel_id_service->GetUniqueID());
   globals_->dns_probe_service.reset(new chrome_browser_net::DnsProbeService());
   globals_->host_mapping_rules.reset(new net::HostMappingRules());
   params_.host_mapping_rules = globals_->host_mapping_rules.get();
@@ -668,13 +670,6 @@
       command_line, is_quic_allowed_by_policy_,
       http_09_on_non_default_ports_enabled_, &params_);
 
-  TRACE_EVENT_BEGIN0("startup",
-                     "IOThread::Init:ProxyScriptFetcherRequestContext");
-  globals_->proxy_script_fetcher_context.reset(
-      ConstructProxyScriptFetcherContext(globals_, params_, net_log_));
-  TRACE_EVENT_END0("startup",
-                   "IOThread::Init:ProxyScriptFetcherRequestContext");
-
 #if defined(OS_MACOSX)
   // Start observing Keychain events. This needs to be done on the UI thread,
   // as Keychain services requires a CFRunLoop.
@@ -683,26 +678,14 @@
                           base::Bind(&ObserveKeychainEvents));
 #endif
 
-  // InitSystemRequestContext turns right around and posts a task back
-  // to the IO thread, so we can't let it run until we know the IO
-  // thread has started.
-  //
-  // Note that since we are at BrowserThread::Init time, the UI thread
-  // is blocked waiting for the thread to start.  Therefore, posting
-  // this task to the main thread's message loop here is guaranteed to
-  // get it onto the message loop while the IOThread object still
-  // exists.  However, the message might not be processed on the UI
-  // thread until after IOThread is gone, so use a weak pointer.
-  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
-                          base::BindOnce(&IOThread::InitSystemRequestContext,
-                                         weak_factory_.GetWeakPtr()));
-
 #if defined(OS_ANDROID) && defined(ARCH_CPU_ARMEL)
   // Record how common CPUs with broken NEON units are. See
   // https://crbug.com/341598.
   crypto::EnsureOpenSSLInit();
   UMA_HISTOGRAM_BOOLEAN("Net.HasBrokenNEON", CRYPTO_has_broken_NEON());
 #endif
+
+  ConstructSystemRequestContext();
 }
 
 void IOThread::CleanUp() {
@@ -712,19 +695,19 @@
   net::ShutdownNSSHttpIO();
 #endif
 
-#if defined(OS_ANDROID)
-  net::CertVerifyProcAndroid::ShutdownCertNetFetcher();
-#endif
-
   system_url_request_context_getter_ = NULL;
 
   // Unlink the ct_tree_tracker_ from the global cert_transparency_verifier
   // and unregister it from new STH notifications so it will take no actions
   // on anything observed during CleanUp process.
-  globals()->cert_transparency_verifier->SetObserver(nullptr);
-  UnregisterSTHObserver(ct_tree_tracker_.get());
-
-  ct_tree_tracker_.reset();
+  //
+  // Null checks are just for tests that use TestingIOThreadState.
+  if (globals()->cert_transparency_verifier)
+    globals()->cert_transparency_verifier->SetObserver(nullptr);
+  if (ct_tree_tracker_.get()) {
+    UnregisterSTHObserver(ct_tree_tracker_.get());
+    ct_tree_tracker_.reset();
+  }
 
   // Release objects that the net::URLRequestContext could have been pointing
   // to.
@@ -830,8 +813,9 @@
 void IOThread::DisableQuic() {
   params_.enable_quic = false;
 
-  if (globals_->system_http_network_session)
-    globals_->system_http_network_session->DisableQuic();
+  if (globals_->system_request_context_storage)
+    globals_->system_request_context_storage->http_network_session()
+        ->DisableQuic();
 
   if (globals_->proxy_script_fetcher_http_network_session)
     globals_->proxy_script_fetcher_http_network_session->DisableQuic();
@@ -853,41 +837,6 @@
   ClearHostCache(base::Callback<bool(const std::string&)>());
 }
 
-void IOThread::InitSystemRequestContext() {
-  if (system_url_request_context_getter_.get())
-    return;
-  // If we're in unit_tests, IOThread may not be run.
-  if (!BrowserThread::IsMessageLoopValid(BrowserThread::IO))
-    return;
-  system_proxy_config_service_ = ProxyServiceFactory::CreateProxyConfigService(
-      pref_proxy_config_tracker_.get());
-  system_url_request_context_getter_ =
-      new SystemURLRequestContextGetter(this);
-  // Safe to post an unretained this pointer, since IOThread is
-  // guaranteed to outlive the IO BrowserThread.
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
-      base::BindOnce(&IOThread::InitSystemRequestContextOnIOThread,
-                     base::Unretained(this)));
-}
-
-void IOThread::InitSystemRequestContextOnIOThread() {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK(!globals_->system_proxy_service.get());
-  DCHECK(system_proxy_config_service_.get());
-
-  const base::CommandLine& command_line =
-      *base::CommandLine::ForCurrentProcess();
-  globals_->system_proxy_service = ProxyServiceFactory::CreateProxyService(
-      net_log_, globals_->proxy_script_fetcher_context.get(),
-      globals_->system_network_delegate.get(),
-      std::move(system_proxy_config_service_), command_line,
-      WpadQuickCheckEnabled(), PacHttpsUrlStrippingEnabled());
-
-  globals_->system_request_context.reset(
-      ConstructSystemRequestContext(globals_, params_, net_log_));
-}
-
 void IOThread::UpdateDnsClientEnabled() {
   globals()->host_resolver->SetDnsClientEnabled(*dns_client_enabled_);
 }
@@ -908,58 +857,77 @@
   return pac_https_url_stripping_enabled_.GetValue();
 }
 
-// static
-net::URLRequestContext* IOThread::ConstructSystemRequestContext(
-    IOThread::Globals* globals,
-    const net::HttpNetworkSession::Params& params,
-    net::NetLog* net_log) {
-  net::URLRequestContext* context = new SystemURLRequestContext;
+void IOThread::ConstructSystemRequestContext() {
+  globals_->system_request_context =
+      base::MakeUnique<SystemURLRequestContext>();
+  net::URLRequestContext* context = globals_->system_request_context.get();
+  globals_->system_request_context_storage =
+      base::MakeUnique<net::URLRequestContextStorage>(context);
+  net::URLRequestContextStorage* context_storage =
+      globals_->system_request_context_storage.get();
 
   context->set_network_quality_estimator(
-      globals->network_quality_estimator.get());
-  context->set_enable_brotli(globals->enable_brotli);
+      globals_->network_quality_estimator.get());
+  context->set_enable_brotli(globals_->enable_brotli);
   context->set_name("system");
 
   context->set_http_user_agent_settings(
-      globals->http_user_agent_settings.get());
-  context->set_network_delegate(globals->system_network_delegate.get());
-  context->set_net_log(net_log);
-  context->set_host_resolver(globals->host_resolver.get());
-  context->set_proxy_service(globals->system_proxy_service.get());
-  context->set_ssl_config_service(globals->ssl_config_service.get());
+      globals_->http_user_agent_settings.get());
+  context->set_network_delegate(globals_->system_network_delegate.get());
+  context->set_net_log(net_log_);
+  context->set_host_resolver(globals_->host_resolver.get());
+
+  context->set_ssl_config_service(globals_->ssl_config_service.get());
   context->set_http_auth_handler_factory(
-      globals->http_auth_handler_factory.get());
+      globals_->http_auth_handler_factory.get());
 
-  context->set_cookie_store(globals->system_cookie_store.get());
-  context->set_channel_id_service(
-      globals->system_channel_id_service.get());
+  // In-memory cookie store.
+  context_storage->set_cookie_store(
+      content::CreateCookieStore(content::CookieStoreConfig()));
+  // In-memory channel ID store.
+  context_storage->set_channel_id_service(
+      base::MakeUnique<net::ChannelIDService>(
+          new net::DefaultChannelIDStore(nullptr)));
+  context->cookie_store()->SetChannelIDServiceID(
+      context->channel_id_service()->GetUniqueID());
+
   context->set_transport_security_state(
-      globals->transport_security_state.get());
+      globals_->transport_security_state.get());
 
-  context->set_http_server_properties(globals->http_server_properties.get());
+  context->set_http_server_properties(globals_->http_server_properties.get());
 
-  context->set_cert_verifier(globals->cert_verifier.get());
+  context->set_cert_verifier(globals_->cert_verifier.get());
   context->set_cert_transparency_verifier(
-      globals->cert_transparency_verifier.get());
-  context->set_ct_policy_enforcer(globals->ct_policy_enforcer.get());
+      globals_->cert_transparency_verifier.get());
+  context->set_ct_policy_enforcer(globals_->ct_policy_enforcer.get());
 
-  net::HttpNetworkSession::Params system_params(params);
+  TRACE_EVENT_BEGIN0("startup",
+                     "IOThread::Init:ProxyScriptFetcherRequestContext");
+  globals_->proxy_script_fetcher_context.reset(
+      ConstructProxyScriptFetcherContext(globals_, params_, net_log_));
+  TRACE_EVENT_END0("startup",
+                   "IOThread::Init:ProxyScriptFetcherRequestContext");
+
+  const base::CommandLine& command_line =
+      *base::CommandLine::ForCurrentProcess();
+  context_storage->set_proxy_service(ProxyServiceFactory::CreateProxyService(
+      net_log_, globals_->proxy_script_fetcher_context.get(),
+      globals_->system_network_delegate.get(),
+      std::move(system_proxy_config_service_), command_line,
+      WpadQuickCheckEnabled(), PacHttpsUrlStrippingEnabled()));
+
+  net::HttpNetworkSession::Params system_params(params_);
   net::URLRequestContextBuilder::SetHttpNetworkSessionComponents(
       context, &system_params);
 
-  globals->system_http_network_session.reset(
-      new net::HttpNetworkSession(system_params));
-  globals->system_http_transaction_factory.reset(
-      new net::HttpNetworkLayer(globals->system_http_network_session.get()));
+  context_storage->set_http_network_session(
+      base::MakeUnique<net::HttpNetworkSession>(system_params));
+  context_storage->set_http_transaction_factory(
+      base::MakeUnique<net::HttpNetworkLayer>(
+          context_storage->http_network_session()));
 
-  context->set_http_transaction_factory(
-      globals->system_http_transaction_factory.get());
-
-  globals->system_url_request_job_factory.reset(
-      new net::URLRequestJobFactoryImpl());
-  context->set_job_factory(globals->system_url_request_job_factory.get());
-
-  return context;
+  context_storage->set_job_factory(
+      base::MakeUnique<net::URLRequestJobFactoryImpl>());
 }
 
 // static
@@ -1078,9 +1046,9 @@
   context->set_job_factory(
       globals->proxy_script_fetcher_url_request_job_factory.get());
 
-  context->set_cookie_store(globals->system_cookie_store.get());
+  context->set_cookie_store(globals->system_request_context->cookie_store());
   context->set_channel_id_service(
-      globals->system_channel_id_service.get());
+      globals->system_request_context->channel_id_service());
   context->set_network_delegate(globals->system_network_delegate.get());
   context->set_http_user_agent_settings(
       globals->http_user_agent_settings.get());
diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h
index a1069e3f..4b83d2b 100644
--- a/chrome/browser/io_thread.h
+++ b/chrome/browser/io_thread.h
@@ -37,7 +37,6 @@
 class PrefProxyConfigTracker;
 class PrefService;
 class PrefRegistrySimple;
-class SystemURLRequestContextGetter;
 
 #if defined(OS_ANDROID)
 namespace chrome {
@@ -55,6 +54,10 @@
 class TreeStateTracker;
 }
 
+namespace chrome {
+class TestingIOThreadState;
+}
+
 namespace chrome_browser_net {
 class DnsProbeService;
 }
@@ -74,8 +77,6 @@
 namespace net {
 class CTPolicyEnforcer;
 class CertVerifier;
-class ChannelIDService;
-class CookieStore;
 class CTLogVerifier;
 class HostMappingRules;
 class HostResolver;
@@ -92,6 +93,7 @@
 class TransportSecurityState;
 class URLRequestContext;
 class URLRequestContextGetter;
+class URLRequestContextStorage;
 class URLRequestJobFactory;
 
 namespace ct {
@@ -148,8 +150,6 @@
     std::unique_ptr<net::NetworkDelegate> system_network_delegate;
     std::unique_ptr<net::HostResolver> host_resolver;
     std::unique_ptr<net::CertVerifier> cert_verifier;
-    // The ChannelIDService must outlive the HttpTransactionFactory.
-    std::unique_ptr<net::ChannelIDService> system_channel_id_service;
     // This TransportSecurityState doesn't load or save any state. It's only
     // used to enforce pinning for system requests and will only use built-in
     // pins.
@@ -175,16 +175,10 @@
     // |proxy_script_fetcher_context| for the second context. It has a direct
     // ProxyService, since we always directly connect to fetch the PAC script.
     std::unique_ptr<net::URLRequestContext> proxy_script_fetcher_context;
-    std::unique_ptr<net::ProxyService> system_proxy_service;
-    std::unique_ptr<net::HttpNetworkSession> system_http_network_session;
-    std::unique_ptr<net::HttpTransactionFactory>
-        system_http_transaction_factory;
-    std::unique_ptr<net::URLRequestJobFactory> system_url_request_job_factory;
+    std::unique_ptr<net::URLRequestContextStorage>
+        system_request_context_storage;
     std::unique_ptr<net::URLRequestContext> system_request_context;
     SystemRequestContextLeakChecker system_request_context_leak_checker;
-    // |system_cookie_store| and |system_channel_id_service| are shared
-    // between |proxy_script_fetcher_context| and |system_request_context|.
-    std::unique_ptr<net::CookieStore> system_cookie_store;
 #if BUILDFLAG(ENABLE_EXTENSIONS)
     scoped_refptr<extensions::EventRouterForwarder>
         extension_event_router_forwarder;
@@ -269,11 +263,8 @@
   bool PacHttpsUrlStrippingEnabled() const;
 
  private:
-  // Provide SystemURLRequestContextGetter with access to
-  // InitSystemRequestContext().
-  friend class SystemURLRequestContextGetter;
-
   friend class test::IOThreadPeer;
+  friend class chrome::TestingIOThreadState;
 
   // BrowserThreadDelegate implementation, runs on the IO thread.
   // This handles initialization and destruction of state that must
@@ -281,16 +272,6 @@
   void Init() override;
   void CleanUp() override;
 
-  // Global state must be initialized on the IO thread, then this
-  // method must be invoked on the UI thread.
-  void InitSystemRequestContext();
-
-  // Lazy initialization of system request context for
-  // SystemURLRequestContextGetter. To be called on IO thread only
-  // after global state has been initialized on the IO thread, and
-  // SystemRequestContext state has been initialized on the UI thread.
-  void InitSystemRequestContextOnIOThread();
-
   void CreateDefaultAuthHandlerFactory();
 
   // Returns an SSLConfigService instance.
@@ -312,10 +293,7 @@
     return NULL;
 #endif
   }
-  static net::URLRequestContext* ConstructSystemRequestContext(
-      IOThread::Globals* globals,
-      const net::HttpNetworkSession::Params& params,
-      net::NetLog* net_log);
+  void ConstructSystemRequestContext();
 
   // Parse command line flags and use components/network_session_configurator to
   // configure |params|.
diff --git a/chrome/browser/net/predictor.cc b/chrome/browser/net/predictor.cc
index 2bb7698..5d73b27 100644
--- a/chrome/browser/net/predictor.cc
+++ b/chrome/browser/net/predictor.cc
@@ -242,17 +242,19 @@
     const GURL& first_party_for_cookies) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
          BrowserThread::CurrentlyOn(BrowserThread::IO));
-  if (!PredictorEnabled() || !url.is_valid() ||
-      !url.has_host())
+  if (!PredictorEnabled())
     return;
   if (!CanPreresolveAndPreconnect())
     return;
-
+  const GURL canonicalized_url = CanonicalizeUrl(url);
+  if (!canonicalized_url.is_valid() || !canonicalized_url.has_host())
+    return;
   UrlInfo::ResolutionMotivation motivation(UrlInfo::EARLY_LOAD_MOTIVATED);
   const int kConnectionsNeeded = 1;
-  PreconnectUrl(CanonicalizeUrl(url), first_party_for_cookies, motivation,
+  PreconnectUrl(canonicalized_url, first_party_for_cookies, motivation,
                 kConnectionsNeeded, kAllowCredentialsOnPreconnectByDefault);
-  PredictFrameSubresources(url.GetWithEmptyPath(), first_party_for_cookies);
+  PredictFrameSubresources(canonicalized_url.GetWithEmptyPath(),
+                           first_party_for_cookies);
 }
 
 std::vector<GURL> Predictor::GetPredictedUrlListAtStartup(
@@ -725,6 +727,7 @@
                               int count) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
          BrowserThread::CurrentlyOn(BrowserThread::IO));
+  DCHECK(url.is_valid());
 
   if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
     PreconnectUrlOnIOThread(url, first_party_for_cookies, motivation,
diff --git a/chrome/browser/net/predictor_browsertest.cc b/chrome/browser/net/predictor_browsertest.cc
index 7df1e68..add17de9 100644
--- a/chrome/browser/net/predictor_browsertest.cc
+++ b/chrome/browser/net/predictor_browsertest.cc
@@ -321,6 +321,7 @@
       chrome_browser_net::UrlInfo::ResolutionMotivation motivation,
       int count) override {
     base::AutoLock lock(lock_);
+    preconnect_url_attempts_.insert(original_url);
     if (original_url == cross_site_host_) {
       cross_site_preconnected_ = std::max(cross_site_preconnected_, count);
     } else if (original_url == source_host_) {
@@ -412,6 +413,11 @@
     return HasHostBeenLookedUpLocked(url);
   }
 
+  bool HasHostAttemptedToPreconnect(const GURL& url) {
+    base::AutoLock lock(lock_);
+    return base::ContainsKey(preconnect_url_attempts_, url);
+  }
+
   void CheckForWaitingLoop() {
     lock_.AssertAcquired();
     if (waiting_on_dns_.is_empty())
@@ -466,6 +472,7 @@
   int cross_site_preconnected_;
   int same_site_preconnected_;
 
+  std::set<GURL> preconnect_url_attempts_;
   std::set<GURL> successful_dns_lookups_;
   std::set<GURL> unsuccessful_dns_lookups_;
   base::RunLoop* dns_run_loop_;
@@ -876,6 +883,18 @@
   connection_listener_->WaitForAcceptedConnectionsOnUI(4u);
 }
 
+// Regression test for crbug.com/721981.
+IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, PreconnectNonHttpScheme) {
+  GURL url("chrome-native://dummyurl");
+  predictor()->PreconnectUrlAndSubresources(url, GURL());
+  base::RunLoop().RunUntilIdle();
+  // Since |url| is non-HTTP(s) scheme, Predictor will canonicalize it to an
+  // empty url. Make sure that there is no attempt to preconnect |url| or an
+  // empty url.
+  EXPECT_FALSE(observer()->HasHostAttemptedToPreconnect(url));
+  EXPECT_FALSE(observer()->HasHostAttemptedToPreconnect(GURL()));
+}
+
 // Test the html test harness used to initiate cross site fetches. These
 // initiate cross site subresource requests to the cross site test server.
 // Inspect the predictor's internal state to make sure that they are properly
diff --git a/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer.cc
index cb4ad34..b1b2c49 100644
--- a/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer.cc
@@ -117,8 +117,6 @@
     UMA_HISTOGRAM_BOOLEAN(
         "PageLoad.Clients.Ads.Google.Navigations.NonAdFrameRenavigatedToAd",
         FrameIsAd(navigation_handle));
-  } else if (navigation_handle->IsParentMainFrame()) {
-    top_level_subframe_count_ += 1;
   }
 
   // Determine who the parent frame's ad ancestor is.
@@ -135,9 +133,6 @@
 
   ad_frames_data_[frame_tree_node_id] = ad_data;
 
-  if (navigation_handle->IsParentMainFrame() && ad_data)
-    top_level_ad_frame_count_ += 1;
-
   ProcessOngoingNavigationResource(frame_tree_node_id);
   return CONTINUE_OBSERVING;
 }
@@ -215,18 +210,15 @@
   if (page_bytes_ == 0)
     return;
 
+  int non_zero_ad_frames = 0;
   size_t total_ad_frame_bytes = 0;
   size_t uncached_ad_frame_bytes = 0;
 
-  UMA_HISTOGRAM_COUNTS_1000(
-      "PageLoad.Clients.Ads.Google.FrameCounts.AnyParentFrame.AdFrames",
-      ad_frames_data_storage_.size());
-
-  // Don't post UMA for pages that don't have ads.
-  if (ad_frames_data_storage_.empty())
-    return;
-
   for (const AdFrameData& ad_frame_data : ad_frames_data_storage_) {
+    if (ad_frame_data.frame_bytes == 0)
+      continue;
+
+    non_zero_ad_frames += 1;
     total_ad_frame_bytes += ad_frame_data.frame_bytes;
     uncached_ad_frame_bytes += ad_frame_data.frame_bytes_uncached;
 
@@ -236,24 +228,18 @@
     PAGE_BYTES_HISTOGRAM(
         "PageLoad.Clients.Ads.Google.Bytes.AdFrames.PerFrame.Network",
         ad_frame_data.frame_bytes_uncached);
-    if (ad_frame_data.frame_bytes > 0) {
-      UMA_HISTOGRAM_PERCENTAGE(
-          "PageLoad.Clients.Ads.Google.Bytes.AdFrames.PerFrame.PercentNetwork",
-          ad_frame_data.frame_bytes_uncached * 100 / ad_frame_data.frame_bytes);
-    }
+    UMA_HISTOGRAM_PERCENTAGE(
+        "PageLoad.Clients.Ads.Google.Bytes.AdFrames.PerFrame.PercentNetwork",
+        ad_frame_data.frame_bytes_uncached * 100 / ad_frame_data.frame_bytes);
   }
 
   UMA_HISTOGRAM_COUNTS_1000(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.TotalFrames",
-      top_level_subframe_count_);
-  UMA_HISTOGRAM_COUNTS_1000(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.AdFrames",
-      top_level_ad_frame_count_);
+      "PageLoad.Clients.Ads.Google.FrameCounts.AnyParentFrame.AdFrames",
+      non_zero_ad_frames);
 
-  DCHECK_LT(0, top_level_subframe_count_);  // Because ad frames isn't empty.
-  UMA_HISTOGRAM_PERCENTAGE(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.PercentAdFrames",
-      top_level_ad_frame_count_ * 100 / top_level_subframe_count_);
+  // Don't post UMA for pages that don't have ads.
+  if (non_zero_ad_frames == 0)
+    return;
 
   PAGE_BYTES_HISTOGRAM(
       "PageLoad.Clients.Ads.Google.Bytes.NonAdFrames.Aggregate.Total",
diff --git a/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer.h
index f51ef0b..af15657 100644
--- a/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer.h
@@ -73,8 +73,6 @@
 
   size_t page_bytes_ = 0u;
   size_t uncached_page_bytes_ = 0u;
-  int top_level_subframe_count_ = 0;
-  int top_level_ad_frame_count_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(AdsPageLoadMetricsObserver);
 };
diff --git a/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer_unittest.cc
index 79c9fae..c492069 100644
--- a/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/ads_page_load_metrics_observer_unittest.cc
@@ -152,14 +152,6 @@
   // 20KB total were loaded from network, one of which was in an ad frame.
   histogram_tester().ExpectUniqueSample(
       "PageLoad.Clients.Ads.Google.FrameCounts.AnyParentFrame.AdFrames", 1, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.TotalFrames", 1,
-      1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.AdFrames", 1, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.PercentAdFrames",
-      100, 1);
 
   // Individual Ad Frame Metrics
   histogram_tester().ExpectUniqueSample(
@@ -212,8 +204,8 @@
   RenderFrameHost* nested_ad_frame4 = CreateAndNavigateSubFrame(
       "https://tpc.googlesyndication.com/safeframe/2", "", ad_frame4);
 
-  // Create an addditional ad frame without content, it shouldn't be counted
-  // in some percentage calculations.
+  // Create an addditional ad frame without content. It shouldn't be counted
+  // as an ad frame.
   CreateAndNavigateSubFrame(kAdUrl, kNonAdName, main_frame);
 
   // 70KB total in page, 50 from ads, 40 from network, and 30 of those
@@ -237,7 +229,7 @@
   histogram_tester().ExpectBucketCount(
       "PageLoad.Clients.Ads.Google.Bytes.AdFrames.PerFrame.Network", 10, 3);
   histogram_tester().ExpectBucketCount(
-      "PageLoad.Clients.Ads.Google.Bytes.AdFrames.PerFrame.Network", 0, 2);
+      "PageLoad.Clients.Ads.Google.Bytes.AdFrames.PerFrame.Network", 0, 1);
   histogram_tester().ExpectBucketCount(
       "PageLoad.Clients.Ads.Google.Bytes.AdFrames.PerFrame.PercentNetwork", 0,
       1);
@@ -250,15 +242,7 @@
 
   // Counts
   histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.AnyParentFrame.AdFrames", 5, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.TotalFrames", 6,
-      1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.AdFrames", 5, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.PercentAdFrames",
-      83, 1);
+      "PageLoad.Clients.Ads.Google.FrameCounts.AnyParentFrame.AdFrames", 4, 1);
 
   // Page percentages
   histogram_tester().ExpectUniqueSample(
@@ -349,14 +333,6 @@
   // Counts
   histogram_tester().ExpectUniqueSample(
       "PageLoad.Clients.Ads.Google.FrameCounts.AnyParentFrame.AdFrames", 1, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.TotalFrames", 1,
-      1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.AdFrames", 1, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.PercentAdFrames",
-      100, 1);
 
   // Page percentages
   histogram_tester().ExpectUniqueSample(
@@ -419,14 +395,6 @@
   // Counts
   histogram_tester().ExpectUniqueSample(
       "PageLoad.Clients.Ads.Google.FrameCounts.AnyParentFrame.AdFrames", 2, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.TotalFrames", 1,
-      1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.AdFrames", 1, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.PercentAdFrames",
-      100, 1);
 
   // Page percentages
   histogram_tester().ExpectUniqueSample(
@@ -560,14 +528,6 @@
   // Counts
   histogram_tester().ExpectUniqueSample(
       "PageLoad.Clients.Ads.Google.FrameCounts.AnyParentFrame.AdFrames", 1, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.TotalFrames", 1,
-      1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.AdFrames", 1, 1);
-  histogram_tester().ExpectUniqueSample(
-      "PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.PercentAdFrames",
-      100, 1);
 
   // Page percentages
   histogram_tester().ExpectUniqueSample(
diff --git a/chrome/browser/profiles/profile.cc b/chrome/browser/profiles/profile.cc
index 0da71217..27d53ff 100644
--- a/chrome/browser/profiles/profile.cc
+++ b/chrome/browser/profiles/profile.cc
@@ -17,7 +17,7 @@
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_prefs.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/sync/base/sync_prefs.h"
 #include "content/public/browser/host_zoom_map.h"
 #include "content/public/browser/notification_service.h"
diff --git a/chrome/browser/safe_browsing/certificate_reporting_service.cc b/chrome/browser/safe_browsing/certificate_reporting_service.cc
index f75d1ce..31854c5 100644
--- a/chrome/browser/safe_browsing/certificate_reporting_service.cc
+++ b/chrome/browser/safe_browsing/certificate_reporting_service.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "components/certificate_reporting/error_report.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 
 namespace {
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.cc
deleted file mode 100644
index a5e6ab2..0000000
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.h"
-
-#include "base/strings/utf_string_conversions.h"
-
-namespace safe_browsing {
-
-namespace {
-
-// Some dummy strings to be displayed in the Cleaner dialog while iterating on
-// the dialog's UX design and work on the Chrome<->Cleaner IPC is ongoing.
-constexpr char kWindowTitle[] = "Clean up your computer?";
-constexpr char kMainText[] =
-    "Chrome found software that harms your browsing experience. Remove related "
-    "files from your computer and restore browser settings, including your "
-    "search engine and home page.";
-constexpr char kAcceptButtonLabel[] = "Cleanup";
-constexpr char kAdvancedButtonLabel[] = "Advanced";
-
-}  // namespace
-
-ChromeCleanerDialogController::ChromeCleanerDialogController() {}
-
-ChromeCleanerDialogController::~ChromeCleanerDialogController() = default;
-
-base::string16 ChromeCleanerDialogController::GetWindowTitle() const {
-  return base::UTF8ToUTF16(kWindowTitle);
-}
-
-base::string16 ChromeCleanerDialogController::GetMainText() const {
-  return base::UTF8ToUTF16(kMainText);
-}
-
-base::string16 ChromeCleanerDialogController::GetAcceptButtonLabel() const {
-  return base::UTF8ToUTF16(kAcceptButtonLabel);
-}
-
-base::string16 ChromeCleanerDialogController::GetAdvancedButtonLabel() const {
-  return base::UTF8ToUTF16(kAdvancedButtonLabel);
-}
-
-void ChromeCleanerDialogController::DialogShown() {}
-
-void ChromeCleanerDialogController::Accept() {
-  OnInteractionDone();
-}
-
-void ChromeCleanerDialogController::Cancel() {
-  OnInteractionDone();
-}
-
-void ChromeCleanerDialogController::Close() {
-  OnInteractionDone();
-}
-
-void ChromeCleanerDialogController::AdvancedButtonClicked() {
-  OnInteractionDone();
-}
-
-void ChromeCleanerDialogController::OnInteractionDone() {
-  delete this;
-}
-
-}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.cc
new file mode 100644
index 0000000..b2bc68bd
--- /dev/null
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.cc
@@ -0,0 +1,57 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.h"
+
+#include "chrome/grit/chromium_strings.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace safe_browsing {
+
+ChromeCleanerDialogController::ChromeCleanerDialogController() {}
+
+ChromeCleanerDialogController::~ChromeCleanerDialogController() = default;
+
+base::string16 ChromeCleanerDialogController::GetWindowTitle() const {
+  return l10n_util::GetStringUTF16(IDS_CHROME_CLEANUP_PROMPT_TITLE);
+}
+
+base::string16 ChromeCleanerDialogController::GetMainText() const {
+  return l10n_util::GetStringUTF16(IDS_CHROME_CLEANUP_PROMPT_EXPLANATION);
+}
+
+base::string16 ChromeCleanerDialogController::GetAcceptButtonLabel() const {
+  return l10n_util::GetStringUTF16(
+      IDS_CHROME_CLEANUP_PROMPT_REMOVE_BUTTON_LABEL);
+}
+
+base::string16 ChromeCleanerDialogController::GetDetailsButtonLabel() const {
+  return l10n_util::GetStringUTF16(
+      IDS_CHROME_CLEANUP_PROMPT_DETAILS_BUTTON_LABEL);
+}
+
+void ChromeCleanerDialogController::DialogShown() {}
+
+void ChromeCleanerDialogController::Accept() {
+  OnInteractionDone();
+}
+
+void ChromeCleanerDialogController::Cancel() {
+  OnInteractionDone();
+}
+
+void ChromeCleanerDialogController::Close() {
+  OnInteractionDone();
+}
+
+void ChromeCleanerDialogController::DetailsButtonClicked() {
+  OnInteractionDone();
+}
+
+void ChromeCleanerDialogController::OnInteractionDone() {
+  delete this;
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.h b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.h
similarity index 87%
rename from chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.h
rename to chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.h
index 60d359b..cda8280f 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_DIALOG_CONTROLLER_H_
-#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_DIALOG_CONTROLLER_H_
+#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_DIALOG_CONTROLLER_WIN_H_
+#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_DIALOG_CONTROLLER_WIN_H_
 
 #include <vector>
 
@@ -26,7 +26,7 @@
   base::string16 GetWindowTitle() const;
   base::string16 GetMainText() const;
   base::string16 GetAcceptButtonLabel() const;
-  base::string16 GetAdvancedButtonLabel() const;
+  base::string16 GetDetailsButtonLabel() const;
 
   // Called by the Cleaner dialog when the dialog has been shown. Used for
   // reporting metrics.
@@ -45,11 +45,11 @@
   // eventually delete itself and no member functions should be called after
   // that.
   void Close();
-  // Called when the advanced button is clicked, after which the dialog will
-  // close. After a call to |AdvancedButtonClicked()|, the controller will
+  // Called when the details button is clicked, after which the dialog will
+  // close. After a call to |DetailsButtonClicked()|, the controller will
   // eventually delete itself and no member functions should be called after
   // that.
-  void AdvancedButtonClicked();
+  void DetailsButtonClicked();
 
  protected:
   ~ChromeCleanerDialogController();
@@ -62,4 +62,4 @@
 
 }  // namespace safe_browsing
 
-#endif  // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_DIALOG_CONTROLLER_H_
+#endif  // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_DIALOG_CONTROLLER_WIN_H_
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc
index 36d420c..ec451294 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc
@@ -41,7 +41,7 @@
 #include "components/chrome_cleaner/public/constants/constants.h"
 #include "components/component_updater/pref_names.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "mojo/edk/embedder/embedder.h"
 #include "mojo/edk/embedder/incoming_broker_client_invitation.h"
 #include "mojo/edk/embedder/scoped_ipc_support.h"
@@ -700,6 +700,11 @@
 }
 
 IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, RunDaily) {
+  // When the kInBrowserCleanerUIFeature feature is enabled, the reporter should
+  // never run daily. Test that case separately.
+  if (in_browser_cleaner_ui_)
+    return;
+
   PrefService* local_state = g_browser_process->local_state();
   local_state->SetBoolean(prefs::kSwReporterPendingPrompt, true);
   SetDaysSinceLastTriggered(kDaysBetweenSuccessfulSwReporterRuns - 1);
@@ -728,6 +733,30 @@
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, InBrowserUINoRunDaily) {
+  // Ensure that the reporter always runs every
+  // kDaysBetweenSuccessfulSwReporterRuns days when kInBrowserCleanerUIFeature
+  // is enabled. The case when kInBrowserCleanerUIFeature is disabled is tested
+  // separately.
+  if (!in_browser_cleaner_ui_)
+    return;
+
+  // Users can have the pending prompt set to true when migrating to the new UI,
+  // but it should be disregarded and the reporter should only be run every
+  // kDaysBetweenSuccessfulSwReporterRuns days.
+  PrefService* local_state = g_browser_process->local_state();
+  local_state->SetBoolean(prefs::kSwReporterPendingPrompt, true);
+  SetDaysSinceLastTriggered(kDaysBetweenSuccessfulSwReporterRuns - 1);
+  ASSERT_GT(kDaysBetweenSuccessfulSwReporterRuns - 1,
+            kDaysBetweenSwReporterRunsForPendingPrompt);
+  RunReporter(chrome_cleaner::kSwReporterNothingFound);
+
+  // Pending prompt is set, but we should not run the reporter since it hasn't
+  // been kDaysBetweenSuccessfulSwReporterRuns days since the last run.
+  ExpectReporterLaunches(0, 0, false);
+  ExpectToRunAgain(1);
+}
+
 IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, ParameterChange) {
   // If the reporter is run several times with different parameters, it should
   // only be launched once, with the last parameter set.
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
index e3cb48c..fcf56b5 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
@@ -973,11 +973,15 @@
     // Run a queue of reporters if none have been triggered in the last
     // |days_between_reporter_runs_| days, which depends if there is a pending
     // prompt to be added to Chrome's menu.
-    if (local_state->GetBoolean(prefs::kSwReporterPendingPrompt)) {
+    //
+    // There is no concept of a pending prompt if the kInBrowserCleanerUIFeature
+    // feature is enabled, so always use kDaysBetweenSuccessfulSwReporterRuns in
+    // that case.
+    days_between_reporter_runs_ = kDaysBetweenSuccessfulSwReporterRuns;
+    if (!base::FeatureList::IsEnabled(kInBrowserCleanerUIFeature) &&
+        local_state->GetBoolean(prefs::kSwReporterPendingPrompt)) {
       days_between_reporter_runs_ = kDaysBetweenSwReporterRunsForPendingPrompt;
       RecordReporterStepHistogram(SW_REPORTER_RAN_DAILY);
-    } else {
-      days_between_reporter_runs_ = kDaysBetweenSuccessfulSwReporterRuns;
     }
     const base::Time now = Now();
     const base::Time last_time_triggered = base::Time::FromInternalValue(
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc
index 1d72cfa..eb5975d 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc
@@ -9,7 +9,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/channel_info.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/version_info/version_info.h"
 
 namespace safe_browsing {
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service.cc b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
index 053284d..0a22684a 100644
--- a/chrome/browser/safe_browsing/chrome_password_protection_service.cc
+++ b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
@@ -14,9 +14,9 @@
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "components/browser_sync/profile_sync_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/password_protection/password_protection_request.h"
 #include "components/safe_browsing_db/database_manager.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 
 using content::BrowserThread;
 
diff --git a/chrome/browser/safe_browsing/client_side_detection_host.cc b/chrome/browser/safe_browsing/client_side_detection_host.cc
index b12aa7d..838bc9d 100644
--- a/chrome/browser/safe_browsing/client_side_detection_host.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_host.cc
@@ -21,10 +21,10 @@
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/common/safebrowsing_messages.h"
 #include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
diff --git a/chrome/browser/safe_browsing/client_side_detection_service.cc b/chrome/browser/safe_browsing/client_side_detection_service.cc
index f2b691b..4fa5786 100644
--- a/chrome/browser/safe_browsing/client_side_detection_service.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_service.cc
@@ -23,9 +23,9 @@
 #include "chrome/common/safe_browsing/client_model.pb.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
 #include "components/prefs/pref_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/common/safebrowsing_messages.h"
 #include "components/safe_browsing/csd.pb.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
diff --git a/chrome/browser/safe_browsing/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection_service.cc
index 6681ae3e..7a886e3 100644
--- a/chrome/browser/safe_browsing/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection_service.cc
@@ -54,9 +54,9 @@
 #include "components/google/core/browser/google_util.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/prefs/pref_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/common/safebrowsing_switches.h"
 #include "components/safe_browsing/csd.pb.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/download_item.h"
 #include "content/public/browser/page_navigator.h"
@@ -948,7 +948,7 @@
 
   // Prepares URLs to be put into a ping message. Currently this just shortens
   // data: URIs, other URLs are included verbatim. If this is a sampled binary,
-  // we'll send a lite-ping which strips all PII.
+  // we'll send a light-ping which strips PII from the URL.
   std::string SanitizeUrl(const GURL& url) const {
     if (type_ == ClientDownloadRequest::SAMPLED_UNSUPPORTED_FILE)
       return url.GetOrigin().spec();
@@ -1022,14 +1022,8 @@
     }
 
     request.set_user_initiated(item_->HasUserGesture());
-    if (type_ == ClientDownloadRequest::SAMPLED_UNSUPPORTED_FILE) {
-      request.set_file_basename(
-          base::FilePath(item_->GetTargetFilePath().Extension())
-              .AsUTF8Unsafe());
-    } else {
-      request.set_file_basename(
+    request.set_file_basename(
         item_->GetTargetFilePath().BaseName().AsUTF8Unsafe());
-    }
     request.set_download_type(type_);
 
     ReferrerChainData* referrer_chain_data =
diff --git a/chrome/browser/safe_browsing/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection_service_unittest.cc
index 94c4c68c..e8d6a59 100644
--- a/chrome/browser/safe_browsing/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection_service_unittest.cc
@@ -38,10 +38,10 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/prefs/pref_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/common/safebrowsing_switches.h"
 #include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/test_database_manager.h"
 #include "content/public/browser/download_danger_type.h"
 #include "content/public/browser/page_navigator.h"
@@ -66,6 +66,7 @@
 using ::testing::ElementsAre;
 using ::testing::Invoke;
 using ::testing::Mock;
+using ::testing::NiceMock;
 using ::testing::NotNull;
 using ::testing::Return;
 using ::testing::ReturnRef;
@@ -193,6 +194,8 @@
   RunLoop run_loop_;
 };
 
+using NiceMockDownloadItem = NiceMock<content::MockDownloadItem>;
+
 }  // namespace
 
 ACTION_P(SetCertificateContents, contents) {
@@ -406,7 +409,7 @@
   }
 
   void PrepareBasicDownloadItem(
-      content::MockDownloadItem* item,
+      NiceMockDownloadItem* item,
       const std::vector<std::string> url_chain_items,
       const std::string& referrer_url,
       const base::FilePath::StringType& tmp_path_literal,
@@ -419,7 +422,7 @@
   }
 
   void PrepareBasicDownloadItemWithFullPaths(
-      content::MockDownloadItem* item,
+      NiceMockDownloadItem* item,
       const std::vector<std::string> url_chain_items,
       const std::string& referrer_url,
       const base::FilePath& tmp_full_path,
@@ -427,17 +430,14 @@
     url_chain_.clear();
     for (std::string item: url_chain_items)
       url_chain_.push_back(GURL(item));
+    if (url_chain_.empty())
+      url_chain_.push_back(GURL());
     referrer_ = GURL(referrer_url);
     tmp_path_ = tmp_full_path;
     final_path_ = final_full_path;
     hash_ = "hash";
 
-    if (url_chain_.size() > 0) {
-      EXPECT_CALL(*item, GetURL()).WillRepeatedly(ReturnRef(url_chain_.back()));
-    } else{
-      GURL empty_url;
-      EXPECT_CALL(*item, GetURL()).WillRepeatedly(ReturnRef(empty_url));
-    }
+    EXPECT_CALL(*item, GetURL()).WillRepeatedly(ReturnRef(url_chain_.back()));
     EXPECT_CALL(*item, GetFullPath()).WillRepeatedly(ReturnRef(tmp_path_));
     EXPECT_CALL(*item, GetTargetFilePath())
         .WillRepeatedly(ReturnRef(final_path_));
@@ -551,7 +551,8 @@
   base::ScopedTempDir profile_dir_;
   std::unique_ptr<TestingProfile> profile_;
   // The following 5 fields are used by PrepareBasicDownloadItem() function to
-  // store attributes of the last download item.
+  // store attributes of the last download item. They can be modified
+  // afterwards and the *item will return the new values.
   std::vector<GURL> url_chain_;
   GURL referrer_;
   base::FilePath tmp_path_;
@@ -567,7 +568,7 @@
       &factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
       net::URLRequestStatus::SUCCESS);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   if (type == ZIP) {
     PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.zip"},  // url_chain
                              "http://www.google.com/",     // referrer
@@ -605,7 +606,7 @@
 }
 
 TEST_F(DownloadProtectionServiceTest, CheckClientDownloadInvalidUrl) {
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   {
     PrepareBasicDownloadItem(&item,
                              std::vector<std::string>(),   // empty url_chain
@@ -638,7 +639,7 @@
 }
 
 TEST_F(DownloadProtectionServiceTest, CheckClientDownloadNotABinary) {
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     std::vector<std::string>(),   // empty url_chain
@@ -662,7 +663,7 @@
       &factory, ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
       net::URLRequestStatus::SUCCESS);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     std::vector<std::string>(),   // empty url_chain
@@ -758,7 +759,7 @@
       &factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
       net::URLRequestStatus::SUCCESS);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     std::vector<std::string>(),   // empty url_chain
@@ -908,20 +909,21 @@
       &factory, ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
       net::URLRequestStatus::SUCCESS);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
-    &item,
-    std::vector<std::string>(),   // empty url_chain
-    "http://www.google.com/",     // referrer
-    FILE_PATH_LITERAL("a.tmp"),   // tmp_path
-    FILE_PATH_LITERAL("a.foobar_unknown_ype"));  // final_path
+      &item,
+      // Add paths so we can check they are properly removed.
+      {"http://referrer.com/1/2", "http://referrer.com/3/4",
+       "http://download.com/path/a.foobar_unknown_type"},
+      "http://referrer.com/3/4",                    // Referrer
+      FILE_PATH_LITERAL("a.tmp"),                   // tmp_path
+      FILE_PATH_LITERAL("a.foobar_unknown_type"));  // final_path
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
                   tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
       .Times(1);
-  url_chain_.push_back(GURL("http://www.whitelist.com/a.foobar_unknown_type"));
 
   // Set ping sample rate to 1.00 so download_service_ will always send a
   // "light" ping for unknown types if allowed.
@@ -953,8 +955,8 @@
     run_loop.Run();
     EXPECT_TRUE(IsResult(DownloadProtectionService::UNKNOWN));
     ASSERT_TRUE(HasClientDownloadRequest());
-    // Verify it's a "light" ping, check that URLs don't have paths, and
-    // and verify filename is just an extension.
+
+    // Verify it's a "light" ping, check that URLs don't have paths.
     auto* req = GetClientDownloadRequest();
     EXPECT_EQ(ClientDownloadRequest::SAMPLED_UNSUPPORTED_FILE,
               req->download_type());
@@ -964,7 +966,6 @@
       EXPECT_EQ(GURL(resource.referrer()).GetOrigin().spec(),
                 resource.referrer());
     }
-    EXPECT_EQ('.', req->file_basename()[0]);
     ClearClientDownloadRequest();
   }
   {
@@ -1003,7 +1004,7 @@
       &factory, ClientDownloadResponse::SAFE, net::HTTP_INTERNAL_SERVER_ERROR,
       net::URLRequestStatus::FAILED);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     {"http://www.evil.com/a.exe"},  // url_chain
@@ -1032,7 +1033,7 @@
   PrepareResponse(&factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
                   net::URLRequestStatus::SUCCESS);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.exe"},  // url_chain
                            "http://www.google.com/",              // referrer
                            FILE_PATH_LITERAL("a.tmp"),            // tmp_path
@@ -1190,7 +1191,7 @@
       &factory, ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
       net::URLRequestStatus::SUCCESS);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(&item,
                            {"http://www.evil.com/a.exe"},  // url_chain
                            "http://www.google.com/",       // referrer
@@ -1223,7 +1224,7 @@
       &factory, ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
       net::URLRequestStatus::SUCCESS);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     {"blob:http://www.evil.com/50b85f60-71e4-11e4-82f8-0800200c9a66"},
@@ -1257,7 +1258,7 @@
       &factory, ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
       net::URLRequestStatus::SUCCESS);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     {"data:text/html:base64,", "data:text/html:base64,blahblahblah",
@@ -1315,7 +1316,7 @@
       &factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
       net::URLRequestStatus::SUCCESS);
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     {"http://www.evil.com/a.zip"},  // url_chain
@@ -1447,7 +1448,7 @@
 TEST_F(DownloadProtectionServiceTest, CheckClientDownloadValidateRequest) {
   net::TestURLFetcherFactory factory;
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     {"http://www.google.com/",
@@ -1521,7 +1522,7 @@
        CheckClientDownloadValidateRequestNoSignature) {
   net::TestURLFetcherFactory factory;
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     {"http://www.google.com/",
@@ -1581,7 +1582,7 @@
        CheckClientDownloadValidateRequestTabHistory) {
   net::TestURLFetcherFactory factory;
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     {"http://www.google.com/",
@@ -1746,7 +1747,7 @@
   GURL referrer("http://www.google.com/");
   std::string hash = "hash";
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   EXPECT_CALL(item, GetURL()).WillRepeatedly(ReturnRef(url_chain.back()));
   EXPECT_CALL(item, GetUrlChain()).WillRepeatedly(ReturnRef(url_chain));
   EXPECT_CALL(item, GetReferrerUrl()).WillRepeatedly(ReturnRef(referrer));
@@ -1810,7 +1811,7 @@
 TEST_F(DownloadProtectionServiceTest, TestDownloadRequestTimeout) {
   net::TestURLFetcherFactory factory;
 
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     {"http://www.evil.com/bla.exe"},  // url_chain
@@ -1843,7 +1844,7 @@
 
 TEST_F(DownloadProtectionServiceTest, TestDownloadItemDestroyed) {
   {
-    content::MockDownloadItem item;
+    NiceMockDownloadItem item;
     PrepareBasicDownloadItem(
       &item,
       {"http://www.evil.com/bla.exe"},  // url_chain
@@ -1875,8 +1876,7 @@
 TEST_F(DownloadProtectionServiceTest,
        TestDownloadItemDestroyedDuringWhitelistCheck) {
   net::TestURLFetcherFactory factory;
-  std::unique_ptr<content::MockDownloadItem> item(
-      new content::MockDownloadItem);
+  std::unique_ptr<NiceMockDownloadItem> item(new NiceMockDownloadItem);
   PrepareBasicDownloadItem(
     item.get(),
     {"http://www.evil.com/bla.exe"},  // url_chain
@@ -2030,7 +2030,7 @@
 }
 
 TEST_F(DownloadProtectionServiceTest, GetAndSetDownloadPingToken) {
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   EXPECT_TRUE(DownloadProtectionService::GetDownloadPingToken(&item).empty());
   std::string token = "download_ping_token";
   DownloadProtectionService::SetDownloadPingToken(&item, token);
@@ -2296,7 +2296,7 @@
 };
 
 TEST_F(DownloadProtectionServiceFlagTest, CheckClientDownloadOverridenByFlag) {
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
   PrepareBasicDownloadItem(
     &item,
     {"http://www.evil.com/a.exe"},  // url_chain
@@ -2327,7 +2327,7 @@
 // blacklisted by hash.
 TEST_F(DownloadProtectionServiceFlagTest,
        CheckClientDownloadZipOverridenByFlag) {
-  content::MockDownloadItem item;
+  NiceMockDownloadItem item;
 
   PrepareBasicDownloadItemWithFullPaths(
       &item, {"http://www.evil.com/a.exe"},  // url_chain
diff --git a/chrome/browser/safe_browsing/incident_reporting/extension_data_collection_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/extension_data_collection_unittest.cc
index 4d9e89d8..8378b6c 100644
--- a/chrome/browser/safe_browsing/incident_reporting/extension_data_collection_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/extension_data_collection_unittest.cc
@@ -20,8 +20,8 @@
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/csd.pb.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc
index 72329940..0275fdd 100644
--- a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc
@@ -35,9 +35,9 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_service.h"
 #include "net/url_request/url_request_context_getter.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service_unittest.cc
index 63b50d8..9d4d540 100644
--- a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service_unittest.cc
@@ -34,8 +34,8 @@
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/csd.pb.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "extensions/browser/quota_service.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/last_download_finder_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/last_download_finder_unittest.cc
index 033d23c8..d3cf2fd1e 100644
--- a/chrome/browser/safe_browsing/incident_reporting/last_download_finder_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/last_download_finder_unittest.cc
@@ -43,8 +43,8 @@
 #include "components/history/core/browser/history_constants.h"
 #include "components/history/core/browser/history_database_params.h"
 #include "components/history/core/browser/history_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/csd.pb.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
diff --git a/chrome/browser/safe_browsing/local_database_manager.cc b/chrome/browser/safe_browsing/local_database_manager.cc
index 33c0044..7c031ea 100644
--- a/chrome/browser/safe_browsing/local_database_manager.cc
+++ b/chrome/browser/safe_browsing/local_database_manager.cc
@@ -33,8 +33,8 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/common/safebrowsing_switches.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/util.h"
 #include "components/safe_browsing_db/v4_protocol_manager_util.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/chrome/browser/safe_browsing/local_database_manager.h b/chrome/browser/safe_browsing/local_database_manager.h
index ea55fbd..185aa470 100644
--- a/chrome/browser/safe_browsing/local_database_manager.h
+++ b/chrome/browser/safe_browsing/local_database_manager.h
@@ -27,8 +27,8 @@
 #include "base/time/time.h"
 #include "chrome/browser/safe_browsing/protocol_manager.h"
 #include "chrome/browser/safe_browsing/safe_browsing_util.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/database_manager.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/safebrowsing.pb.h"
 #include "components/safe_browsing_db/util.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/safe_browsing/notification_image_reporter.cc b/chrome/browser/safe_browsing/notification_image_reporter.cc
index 00f7a12..5f859e8b 100644
--- a/chrome/browser/safe_browsing/notification_image_reporter.cc
+++ b/chrome/browser/safe_browsing/notification_image_reporter.cc
@@ -18,9 +18,9 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/variations/variations_associated_data.h"
 #include "content/public/browser/browser_thread.h"
 #include "net/base/net_errors.h"
diff --git a/chrome/browser/safe_browsing/notification_image_reporter_unittest.cc b/chrome/browser/safe_browsing/notification_image_reporter_unittest.cc
index 780ff24..031d0b45 100644
--- a/chrome/browser/safe_browsing/notification_image_reporter_unittest.cc
+++ b/chrome/browser/safe_browsing/notification_image_reporter_unittest.cc
@@ -13,8 +13,8 @@
 #include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/csd.pb.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/test_database_manager.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/test_browser_thread_bundle.h"
diff --git a/chrome/browser/safe_browsing/protocol_manager.h b/chrome/browser/safe_browsing/protocol_manager.h
index ae17540..8878df62 100644
--- a/chrome/browser/safe_browsing/protocol_manager.h
+++ b/chrome/browser/safe_browsing/protocol_manager.h
@@ -30,7 +30,7 @@
 #include "chrome/browser/safe_browsing/chunk_range.h"
 #include "chrome/browser/safe_browsing/protocol_parser.h"
 #include "chrome/browser/safe_browsing/safe_browsing_util.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/safebrowsing.pb.h"
 #include "components/safe_browsing_db/util.h"
 #include "net/url_request/url_fetcher_delegate.h"
diff --git a/chrome/browser/safe_browsing/protocol_manager_unittest.cc b/chrome/browser/safe_browsing/protocol_manager_unittest.cc
index 8e47595..51502cbfd 100644
--- a/chrome/browser/safe_browsing/protocol_manager_unittest.cc
+++ b/chrome/browser/safe_browsing/protocol_manager_unittest.cc
@@ -13,7 +13,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "chrome/browser/safe_browsing/chunk.pb.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/safebrowsing.pb.h"
 #include "components/safe_browsing_db/util.h"
 #include "content/public/test/test_browser_thread_bundle.h"
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page.cc
index e866451..f979d96 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page.cc
@@ -17,8 +17,8 @@
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/browser/threat_details.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/triggers/trigger_manager.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/interstitial_page.h"
 #include "content/public/browser/navigation_entry.h"
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
index 42800c98..cfbc904 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
@@ -37,8 +37,8 @@
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/browser/threat_details.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/database_manager.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/test_database_manager.h"
 #include "components/safe_browsing_db/util.h"
 #include "components/security_interstitials/core/controller_client.h"
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.cc
index 5690dac0..3c988fb 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.cc
@@ -16,7 +16,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/browser/threat_details.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "content/public/browser/interstitial_page.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/render_process_host.h"
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.h b/chrome/browser/safe_browsing/safe_browsing_service.h
index c207f65e3..c98e36b 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.h
+++ b/chrome/browser/safe_browsing/safe_browsing_service.h
@@ -20,7 +20,7 @@
 #include "base/observer_list.h"
 #include "base/sequenced_task_runner_helpers.h"
 #include "chrome/browser/safe_browsing/services_delegate.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/util.h"
 #include "components/safe_browsing_db/v4_feature_list.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/chrome/browser/safe_browsing/ui_manager.cc b/chrome/browser/safe_browsing/ui_manager.cc
index d3de4ec9..6cde6d90 100644
--- a/chrome/browser/safe_browsing/ui_manager.cc
+++ b/chrome/browser/safe_browsing/ui_manager.cc
@@ -22,7 +22,7 @@
 #include "chrome/common/url_constants.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/browser/threat_details.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/notification_service.h"
diff --git a/chrome/browser/safe_browsing/ui_manager_unittest.cc b/chrome/browser/safe_browsing/ui_manager_unittest.cc
index 174936a1..b0553ab 100644
--- a/chrome/browser/safe_browsing/ui_manager_unittest.cc
+++ b/chrome/browser/safe_browsing/ui_manager_unittest.cc
@@ -10,7 +10,7 @@
 #include "chrome/browser/safe_browsing/ui_manager.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/util.h"
 #include "components/security_interstitials/core/base_safe_browsing_error_ui.h"
 #include "content/public/browser/navigation_entry.h"
diff --git a/chrome/browser/ssl/bad_clock_blocking_page.cc b/chrome/browser/ssl/bad_clock_blocking_page.cc
index 02a5019..9a972b0 100644
--- a/chrome/browser/ssl/bad_clock_blocking_page.cc
+++ b/chrome/browser/ssl/bad_clock_blocking_page.cc
@@ -15,7 +15,7 @@
 #include "chrome/browser/renderer_preferences_util.h"
 #include "chrome/browser/ssl/cert_report_helper.h"
 #include "chrome/browser/ssl/ssl_cert_reporter.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/security_interstitials/core/bad_clock_ui.h"
 #include "components/security_interstitials/core/metrics_helper.h"
 #include "content/public/browser/interstitial_page.h"
diff --git a/chrome/browser/ssl/captive_portal_blocking_page.cc b/chrome/browser/ssl/captive_portal_blocking_page.cc
index dab9355..066233a3 100644
--- a/chrome/browser/ssl/captive_portal_blocking_page.cc
+++ b/chrome/browser/ssl/captive_portal_blocking_page.cc
@@ -23,7 +23,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/captive_portal/captive_portal_detector.h"
 #include "components/certificate_reporting/error_reporter.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/security_interstitials/core/controller_client.h"
 #include "components/security_interstitials/core/metrics_helper.h"
 #include "components/url_formatter/url_formatter.h"
diff --git a/chrome/browser/ssl/cert_report_helper.cc b/chrome/browser/ssl/cert_report_helper.cc
index 315220d2..053ce92 100644
--- a/chrome/browser/ssl/cert_report_helper.cc
+++ b/chrome/browser/ssl/cert_report_helper.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/ssl/ssl_cert_reporter.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/security_interstitials/core/controller_client.h"
 #include "components/security_interstitials/core/metrics_helper.h"
 #include "components/strings/grit/components_strings.h"
diff --git a/chrome/browser/ssl/certificate_reporting_test_utils.cc b/chrome/browser/ssl/certificate_reporting_test_utils.cc
index 4fbb7e20..ba04b2bfa6 100644
--- a/chrome/browser/ssl/certificate_reporting_test_utils.cc
+++ b/chrome/browser/ssl/certificate_reporting_test_utils.cc
@@ -17,7 +17,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "components/certificate_reporting/error_report.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/variations/variations_associated_data.h"
 #include "net/url_request/report_sender.h"
 #include "net/url_request/url_request_context.h"
diff --git a/chrome/browser/ssl/ssl_blocking_page.cc b/chrome/browser/ssl/ssl_blocking_page.cc
index 8d88825..27a004b7 100644
--- a/chrome/browser/ssl/ssl_blocking_page.cc
+++ b/chrome/browser/ssl/ssl_blocking_page.cc
@@ -23,7 +23,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/security_interstitials/core/controller_client.h"
 #include "components/security_interstitials/core/metrics_helper.h"
 #include "components/security_interstitials/core/ssl_error_ui.h"
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index b608ee7..e90d3dc6 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -548,7 +548,7 @@
     "//components/rappor",
     "//components/renderer_context_menu",
     "//components/resources",
-    "//components/safe_browsing_db:safe_browsing_prefs",
+    "//components/safe_browsing/common:safe_browsing_prefs",
     "//components/safe_json",
     "//components/search",
     "//components/search_engines",
@@ -3087,8 +3087,8 @@
       "input_method/input_method_engine_base.h",
       "network_profile_bubble.cc",
       "network_profile_bubble.h",
-      "views/chrome_cleaner_dialog.cc",
-      "views/chrome_cleaner_dialog.h",
+      "views/chrome_cleaner_dialog_win.cc",
+      "views/chrome_cleaner_dialog_win.h",
       "views/color_chooser_dialog.cc",
       "views/color_chooser_dialog.h",
       "views/critical_notification_bubble_view.cc",
diff --git a/chrome/browser/ui/cocoa/app_menu/app_menu_controller.h b/chrome/browser/ui/cocoa/app_menu/app_menu_controller.h
index b3508a63..5718e87c 100644
--- a/chrome/browser/ui/cocoa/app_menu/app_menu_controller.h
+++ b/chrome/browser/ui/cocoa/app_menu/app_menu_controller.h
@@ -9,6 +9,7 @@
 
 #include <memory>
 
+#include "base/mac/objc_property_releaser.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/time/time.h"
 #import "chrome/browser/ui/cocoa/has_weak_browser_pointer.h"
@@ -131,6 +132,8 @@
 
   MenuTrackedRootView* toolbarActionsOverflowItem_;
   BrowserActionsContainerView* overflowActionsContainerView_;
+
+  base::mac::ObjCPropertyReleaser propertyReleaser_;
 }
 
 @property(retain, nonatomic) IBOutlet MenuTrackedRootView* editItem;
diff --git a/chrome/browser/ui/cocoa/app_menu/app_menu_controller.mm b/chrome/browser/ui/cocoa/app_menu/app_menu_controller.mm
index 6670567..c333a96 100644
--- a/chrome/browser/ui/cocoa/app_menu/app_menu_controller.mm
+++ b/chrome/browser/ui/cocoa/app_menu/app_menu_controller.mm
@@ -8,7 +8,6 @@
 
 #include "base/bind.h"
 #include "base/mac/bundle_locations.h"
-#include "base/mac/objc_release_properties.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_macros.h"
@@ -602,6 +601,7 @@
 - (id)initWithController:(AppMenuController*)controller {
   if ((self = [super initWithNibName:@"AppMenu"
                               bundle:base::mac::FrameworkBundle()])) {
+    propertyReleaser_.Init(self, [AppMenuButtonViewController class]);
     controller_ = controller;
     [[NSNotificationCenter defaultCenter]
         addObserver:self
@@ -614,7 +614,6 @@
 
 - (void)dealloc {
   [[NSNotificationCenter defaultCenter] removeObserver:self];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.mm b/chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.mm
index c04cf5d..5195ecc 100644
--- a/chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.mm
+++ b/chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.mm
@@ -303,6 +303,8 @@
     // the list above.
     content::RenderViewHost* render_view_host =
         web_contents->GetRenderViewHost();
+
+    // TODO(tdresser): get the hardware timestamp from the NSEvent.
     render_view_host->GetWidget()->ForwardKeyboardEvent(
         NativeWebKeyboardEvent(event));
     return YES;
diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h
index 4721370..21ba308 100644
--- a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h
+++ b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h
@@ -68,8 +68,8 @@
 
 + (CGFloat)getContentAreaWidth:(NSRect)cellFrame;
 
-@end
++ (CGFloat)getContentTextHeight;
 
-const CGFloat kContentLineHeight = 25.0;
+@end
 
 #endif  // CHROME_BROWSER_UI_COCOA_OMNIBOX_OMNIBOX_POPUP_CELL_H_
diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm
index e4067979..a41858a 100644
--- a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm
+++ b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm
@@ -11,8 +11,9 @@
 
 #include "base/i18n/rtl.h"
 #include "base/mac/foundation_util.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_nsobject.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
@@ -21,6 +22,7 @@
 #include "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h"
 #import "chrome/browser/ui/cocoa/themed_window.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/browser/omnibox_popup_model.h"
 #include "components/omnibox/browser/suggestion_answer.h"
 #include "skia/ext/skia_utils_mac.h"
@@ -31,14 +33,21 @@
 
 namespace {
 
-// How far to offset text.
-const CGFloat kVerticalTextPadding = 3.0;
+// Extra padding beyond the vertical text padding.
+constexpr CGFloat kMaterialExtraVerticalImagePadding = 2.0;
 
-const CGFloat kMaterialVerticalImagePadding = 5.0;
+constexpr CGFloat kMaterialTextStartOffset = 27.0;
 
-const CGFloat kMaterialTextStartOffset = 27.0;
+constexpr CGFloat kMaterialImageXOffset = 6.0;
 
-const CGFloat kMaterialImageXOffset = 6.0;
+// Returns the margin that should appear at the top and bottom of the result.
+CGFloat GetVerticalMargin() {
+  constexpr CGFloat kDefaultVerticalMargin = 3.0;
+
+  return base::GetFieldTrialParamByFeatureAsInt(
+      omnibox::kUIExperimentVerticalMargin,
+      OmniboxFieldTrial::kUIVerticalMarginParam, kDefaultVerticalMargin);
+}
 
 // Flips the given |rect| in context of the given |frame|.
 NSRect FlipIfRTL(NSRect rect, NSRect frame) {
@@ -343,6 +352,11 @@
 
 }  // namespace
 
+@interface OmniboxPopupCellData () {
+  base::mac::ObjCPropertyReleaser propertyReleaser_OmniboxPopupCellData_;
+}
+@end
+
 @interface OmniboxPopupCell ()
 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString
                withFrame:(NSRect)cellFrame
@@ -413,15 +427,12 @@
       }
       maxLines_ = 1;
     }
+    propertyReleaser_OmniboxPopupCellData_.Init(self,
+                                                [OmniboxPopupCellData class]);
   }
   return self;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (instancetype)copyWithZone:(NSZone*)zone {
   return [self retain];
 }
@@ -478,7 +489,8 @@
       isDarkTheme ? [cellData incognitoImage] : [cellData image];
   imageRect.size = [theImage size];
   imageRect.origin.x += kMaterialImageXOffset + [tableView contentLeftPadding];
-  imageRect.origin.y += kMaterialVerticalImagePadding;
+  imageRect.origin.y +=
+      GetVerticalMargin() + kMaterialExtraVerticalImagePadding;
   [theImage drawInRect:FlipIfRTL(imageRect, cellFrame)
               fromRect:NSZeroRect
              operation:NSCompositeSourceOver
@@ -488,7 +500,7 @@
 
   NSPoint origin =
       NSMakePoint(kMaterialTextStartOffset + [tableView contentLeftPadding],
-                  kVerticalTextPadding);
+                  GetVerticalMargin());
   if ([cellData matchType] == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) {
     // Tail suggestions are rendered with a prefix (usually ellipsis), which
     // appear vertically stacked.
@@ -505,9 +517,9 @@
 
   if (descriptionMaxWidth > 0) {
     if ([cellData isAnswer]) {
-      origin =
-          NSMakePoint(kMaterialTextStartOffset + [tableView contentLeftPadding],
-                      kContentLineHeight - kVerticalTextPadding);
+      origin = NSMakePoint(
+          kMaterialTextStartOffset + [tableView contentLeftPadding],
+          [OmniboxPopupCell getContentTextHeight] - GetVerticalMargin());
       CGFloat imageSize = [tableView answerLineHeight];
       NSRect imageRect =
           NSMakeRect(NSMinX(cellFrame) + origin.x, NSMinY(cellFrame) + origin.y,
@@ -519,7 +531,7 @@
                           respectFlipped:YES
                                    hints:nil];
       if ([cellData answerImage]) {
-        origin.x += imageSize + kMaterialVerticalImagePadding;
+        origin.x += imageSize + kMaterialImageXOffset;
 
         // Have to nudge the baseline down 1pt in Material Design for the text
         // that follows, so that it's the same as the bottom of the image.
@@ -683,4 +695,9 @@
   return NSWidth(cellFrame) - kMaterialTextStartOffset;
 }
 
++ (CGFloat)getContentTextHeight {
+  constexpr CGFloat kDefaultTextHeight = 19;
+  return kDefaultTextHeight + 2 * GetVerticalMargin();
+}
+
 @end
diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_matrix.mm b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_matrix.mm
index dd21664..ca66db2 100644
--- a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_matrix.mm
+++ b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_matrix.mm
@@ -106,7 +106,7 @@
 }
 
 - (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row {
-  CGFloat height = kContentLineHeight;
+  CGFloat height = [OmniboxPopupCell getContentTextHeight];
   if ([[array_ objectAtIndex:row] isAnswer]) {
     OmniboxPopupMatrix* matrix =
         base::mac::ObjCCastStrict<OmniboxPopupMatrix>(tableView);
diff --git a/chrome/browser/ui/views/chrome_cleaner_dialog_browsertest.cc b/chrome/browser/ui/views/chrome_cleaner_dialog_browsertest_win.cc
similarity index 91%
rename from chrome/browser/ui/views/chrome_cleaner_dialog_browsertest.cc
rename to chrome/browser/ui/views/chrome_cleaner_dialog_browsertest_win.cc
index e5f5a56..48c51375 100644
--- a/chrome/browser/ui/views/chrome_cleaner_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/chrome_cleaner_dialog_browsertest_win.cc
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/chrome_cleaner_dialog.h"
+#include "chrome/browser/ui/views/chrome_cleaner_dialog_win.h"
 
 #include "base/macros.h"
-#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
diff --git a/chrome/browser/ui/views/chrome_cleaner_dialog.cc b/chrome/browser/ui/views/chrome_cleaner_dialog_win.cc
similarity index 95%
rename from chrome/browser/ui/views/chrome_cleaner_dialog.cc
rename to chrome/browser/ui/views/chrome_cleaner_dialog_win.cc
index e019847..c6cf1b3 100644
--- a/chrome/browser/ui/views/chrome_cleaner_dialog.cc
+++ b/chrome/browser/ui/views/chrome_cleaner_dialog_win.cc
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/chrome_cleaner_dialog.h"
+#include "chrome/browser/ui/views/chrome_cleaner_dialog_win.h"
 
 #include "base/strings/string16.h"
-#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_win.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -104,7 +104,7 @@
 
 views::View* ChromeCleanerDialog::CreateExtraView() {
   return views::MdTextButton::CreateSecondaryUiButton(
-      this, controller_->GetAdvancedButtonLabel());
+      this, controller_->GetDetailsButtonLabel());
 }
 
 bool ChromeCleanerDialog::Accept() {
@@ -146,7 +146,7 @@
   // TODO(alito): Navigate to the webui version of the Chrome Cleaner UI when
   // that is implemented.
   if (controller_) {
-    controller_->AdvancedButtonClicked();
+    controller_->DetailsButtonClicked();
     controller_ = nullptr;
   }
   GetWidget()->Close();
diff --git a/chrome/browser/ui/views/chrome_cleaner_dialog.h b/chrome/browser/ui/views/chrome_cleaner_dialog_win.h
similarity index 92%
rename from chrome/browser/ui/views/chrome_cleaner_dialog.h
rename to chrome/browser/ui/views/chrome_cleaner_dialog_win.h
index bc62092..4811919 100644
--- a/chrome/browser/ui/views/chrome_cleaner_dialog.h
+++ b/chrome/browser/ui/views/chrome_cleaner_dialog_win.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_VIEWS_CHROME_CLEANER_DIALOG_H_
-#define CHROME_BROWSER_UI_VIEWS_CHROME_CLEANER_DIALOG_H_
+#ifndef CHROME_BROWSER_UI_VIEWS_CHROME_CLEANER_DIALOG_WIN_H_
+#define CHROME_BROWSER_UI_VIEWS_CHROME_CLEANER_DIALOG_WIN_H_
 
 #include <memory>
 
@@ -67,4 +67,4 @@
   DISALLOW_COPY_AND_ASSIGN(ChromeCleanerDialog);
 };
 
-#endif  // CHROME_BROWSER_UI_VIEWS_CHROME_CLEANER_DIALOG_H_
+#endif  // CHROME_BROWSER_UI_VIEWS_CHROME_CLEANER_DIALOG_WIN_H_
diff --git a/chrome/browser/ui/views/download/download_feedback_dialog_view.cc b/chrome/browser/ui/views/download/download_feedback_dialog_view.cc
index 1ba26514..5e80388 100644
--- a/chrome/browser/ui/views/download/download_feedback_dialog_view.cc
+++ b/chrome/browser/ui/views/download/download_feedback_dialog_view.cc
@@ -15,7 +15,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/page_navigator.h"
 #include "ui/base/l10n/l10n_util.h"
diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc
index ac6bae1..ebbd481a 100644
--- a/chrome/browser/ui/views/download/download_item_view.cc
+++ b/chrome/browser/ui/views/download/download_item_view.cc
@@ -41,7 +41,7 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "content/public/browser/download_danger_type.h"
 #include "third_party/icu/source/common/unicode/uchar.h"
 #include "ui/accessibility/ax_node_data.h"
diff --git a/chrome/browser/ui/views/find_bar_host.cc b/chrome/browser/ui/views/find_bar_host.cc
index 87372a11..81c33c9 100644
--- a/chrome/browser/ui/views/find_bar_host.cc
+++ b/chrome/browser/ui/views/find_bar_host.cc
@@ -63,7 +63,9 @@
   // input. Otherwise Up and Down arrow key strokes get eaten. "Nom Nom Nom".
   contents->ClearFocusedElement();
   NativeWebKeyboardEvent event(key_event);
-  contents->GetRenderViewHost()->GetWidget()->ForwardKeyboardEvent(event);
+  contents->GetRenderViewHost()
+      ->GetWidget()
+      ->ForwardKeyboardEventWithLatencyInfo(event, *key_event.latency());
   return true;
 }
 
diff --git a/chrome/browser/ui/views/payments/contact_info_editor_view_controller.cc b/chrome/browser/ui/views/payments/contact_info_editor_view_controller.cc
index 926ef21..f9febb9 100644
--- a/chrome/browser/ui/views/payments/contact_info_editor_view_controller.cc
+++ b/chrome/browser/ui/views/payments/contact_info_editor_view_controller.cc
@@ -27,9 +27,13 @@
     PaymentRequestState* state,
     PaymentRequestDialogView* dialog,
     BackNavigationType back_navigation_type,
+    base::OnceClosure on_edited,
+    base::OnceCallback<void(const autofill::AutofillProfile&)> on_added,
     autofill::AutofillProfile* profile)
     : EditorViewController(spec, state, dialog, back_navigation_type),
-      profile_to_edit_(profile) {}
+      profile_to_edit_(profile),
+      on_edited_(std::move(on_edited)),
+      on_added_(std::move(on_added)) {}
 
 ContactInfoEditorViewController::~ContactInfoEditorViewController() {}
 
@@ -76,12 +80,15 @@
     PopulateProfile(profile_to_edit_);
     state()->GetPersonalDataManager()->UpdateProfile(*profile_to_edit_);
     state()->profile_comparator()->Invalidate(*profile_to_edit_);
+    std::move(on_edited_).Run();
+    on_added_.Reset();
   } else {
     std::unique_ptr<autofill::AutofillProfile> profile =
         base::MakeUnique<autofill::AutofillProfile>();
     PopulateProfile(profile.get());
     state()->GetPersonalDataManager()->AddProfile(*profile);
-    // TODO(crbug.com/712224): Add to profile cache in state_.
+    std::move(on_added_).Run(*profile);
+    on_edited_.Reset();
   }
   return true;
 }
diff --git a/chrome/browser/ui/views/payments/contact_info_editor_view_controller.h b/chrome/browser/ui/views/payments/contact_info_editor_view_controller.h
index b50ec9d..5a0ab4a 100644
--- a/chrome/browser/ui/views/payments/contact_info_editor_view_controller.h
+++ b/chrome/browser/ui/views/payments/contact_info_editor_view_controller.h
@@ -25,11 +25,14 @@
   // Does not take ownership of the arguments, which should outlive this object.
   // Passing nullptr as |profile| indicates that we are editing a new profile;
   // other arguments should never be null.
-  ContactInfoEditorViewController(PaymentRequestSpec* spec,
-                                  PaymentRequestState* state,
-                                  PaymentRequestDialogView* dialog,
-                                  BackNavigationType back_navigation_type,
-                                  autofill::AutofillProfile* profile);
+  ContactInfoEditorViewController(
+      PaymentRequestSpec* spec,
+      PaymentRequestState* state,
+      PaymentRequestDialogView* dialog,
+      BackNavigationType back_navigation_type,
+      base::OnceClosure on_edited,
+      base::OnceCallback<void(const autofill::AutofillProfile&)> on_added,
+      autofill::AutofillProfile* profile);
   ~ContactInfoEditorViewController() override;
 
   // EditorViewController:
@@ -51,11 +54,16 @@
   // Uses the values in the UI fields to populate the corresponding values in
   // |profile|.
   void PopulateProfile(autofill::AutofillProfile* profile);
-
   bool GetSheetId(DialogViewID* sheet_id) override;
 
   autofill::AutofillProfile* profile_to_edit_;
 
+  // Called when |profile_to_edit_| was successfully edited.
+  base::OnceClosure on_edited_;
+  // Called when a new profile was added. The const reference is short-lived,
+  // and the callee should make a copy.
+  base::OnceCallback<void(const autofill::AutofillProfile&)> on_added_;
+
   class ContactInfoValidationDelegate : public ValidationDelegate {
    public:
     ContactInfoValidationDelegate(const EditorField& field,
diff --git a/chrome/browser/ui/views/payments/contact_info_editor_view_controller_browsertest.cc b/chrome/browser/ui/views/payments/contact_info_editor_view_controller_browsertest.cc
index e656af7..dc2cf24 100644
--- a/chrome/browser/ui/views/payments/contact_info_editor_view_controller_browsertest.cc
+++ b/chrome/browser/ui/views/payments/contact_info_editor_view_controller_browsertest.cc
@@ -70,6 +70,11 @@
   EXPECT_EQ(base::ASCIIToUTF16(kEmailAddress),
             profile->GetInfo(autofill::AutofillType(autofill::EMAIL_ADDRESS),
                              GetLocale()));
+
+  PaymentRequest* request = GetPaymentRequests(GetActiveWebContents()).front();
+  EXPECT_EQ(1U, request->state()->contact_profiles().size());
+  EXPECT_EQ(request->state()->contact_profiles().back(),
+            request->state()->selected_contact_profile());
 }
 
 IN_PROC_BROWSER_TEST_F(PaymentRequestContactInfoEditorTest,
@@ -213,4 +218,60 @@
                              GetLocale()));
 }
 
+IN_PROC_BROWSER_TEST_F(PaymentRequestContactInfoEditorTest,
+                       ModifyExistingSelectsIt) {
+  autofill::PersonalDataManager* personal_data_manager = GetDataManager();
+  personal_data_manager->AddObserver(&personal_data_observer_);
+
+  autofill::AutofillProfile incomplete_profile;
+  incomplete_profile.SetInfo(autofill::AutofillType(autofill::NAME_FULL),
+                             base::ASCIIToUTF16(kNameFull), GetLocale());
+  AddAutofillProfile(incomplete_profile);
+
+  autofill::AutofillProfile other_incomplete_profile;
+  other_incomplete_profile.SetInfo(autofill::AutofillType(autofill::NAME_FULL),
+                                   base::ASCIIToUTF16("other"), GetLocale());
+  AddAutofillProfile(other_incomplete_profile);
+
+  InvokePaymentRequestUI();
+  OpenContactInfoScreen();
+
+  PaymentRequest* request = GetPaymentRequests(GetActiveWebContents()).front();
+  EXPECT_EQ(request->state()->contact_profiles().front(),
+            request->state()->selected_contact_profile());
+
+  views::View* list_view = dialog_view()->GetViewByID(
+      static_cast<int>(DialogViewID::CONTACT_INFO_SHEET_LIST_VIEW));
+  DCHECK(list_view);
+  ClickOnDialogViewAndWait(list_view->child_at(1));
+
+  SetEditorTextfieldValue(base::ASCIIToUTF16(kPhoneNumber),
+                          autofill::PHONE_HOME_WHOLE_NUMBER);
+  SetEditorTextfieldValue(base::ASCIIToUTF16(kEmailAddress),
+                          autofill::EMAIL_ADDRESS);
+
+  // Wait until the web database has been updated and the notification sent.
+  base::RunLoop save_data_loop;
+  EXPECT_CALL(personal_data_observer_, OnPersonalDataChanged())
+      .WillOnce(QuitMessageLoop(&save_data_loop));
+  ClickOnDialogViewAndWait(DialogViewID::EDITOR_SAVE_BUTTON);
+  save_data_loop.Run();
+
+  ASSERT_EQ(2UL, personal_data_manager->GetProfiles().size());
+  autofill::AutofillProfile* profile = personal_data_manager->GetProfiles()[0];
+  DCHECK(profile);
+
+  EXPECT_EQ(base::ASCIIToUTF16(kPhoneNumber),
+            profile->GetInfo(
+                autofill::AutofillType(autofill::PHONE_HOME_WHOLE_NUMBER),
+                GetLocale()));
+  EXPECT_EQ(base::ASCIIToUTF16(kEmailAddress),
+            profile->GetInfo(autofill::AutofillType(autofill::EMAIL_ADDRESS),
+                             GetLocale()));
+
+  EXPECT_EQ(2U, request->state()->contact_profiles().size());
+  EXPECT_EQ(request->state()->contact_profiles().back(),
+            request->state()->selected_contact_profile());
+}
+
 }  // namespace payments
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 12c798d..d1f472f 100644
--- a/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
+++ b/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
@@ -277,13 +277,16 @@
 
 void PaymentRequestDialogView::ShowContactInfoEditor(
     BackNavigationType back_navigation_type,
+    base::OnceClosure on_edited,
+    base::OnceCallback<void(const autofill::AutofillProfile&)> on_added,
     autofill::AutofillProfile* profile) {
-  view_stack_->Push(CreateViewAndInstallController(
-                        base::MakeUnique<ContactInfoEditorViewController>(
-                            request_->spec(), request_->state(), this,
-                            back_navigation_type, profile),
-                        &controller_map_),
-                    /* animate = */ true);
+  view_stack_->Push(
+      CreateViewAndInstallController(
+          base::MakeUnique<ContactInfoEditorViewController>(
+              request_->spec(), request_->state(), this, back_navigation_type,
+              std::move(on_edited), std::move(on_added), profile),
+          &controller_map_),
+      /* animate = */ true);
   if (observer_for_testing_)
     observer_for_testing_->OnContactInfoEditorOpened();
 }
diff --git a/chrome/browser/ui/views/payments/payment_request_dialog_view.h b/chrome/browser/ui/views/payments/payment_request_dialog_view.h
index 4e18583..f793e84 100644
--- a/chrome/browser/ui/views/payments/payment_request_dialog_view.h
+++ b/chrome/browser/ui/views/payments/payment_request_dialog_view.h
@@ -136,10 +136,16 @@
       base::OnceCallback<void(const autofill::AutofillProfile&)> on_added,
       autofill::AutofillProfile* profile);
   // |profile| is the profile to be edited, or nullptr for adding a profile.
+  // |on_edited| is called when |profile| was successfully edited, and
+  // |on_added| is called when a new profile was added (the reference is
+  // short-lived; callee should make a copy of the profile object).
   // |back_navigation_type| identifies the type of navigation to execute once
   // the editor has completed successfully.
-  void ShowContactInfoEditor(BackNavigationType back_navigation_type,
-                             autofill::AutofillProfile* profile = nullptr);
+  void ShowContactInfoEditor(
+      BackNavigationType back_navigation_type,
+      base::OnceClosure on_edited,
+      base::OnceCallback<void(const autofill::AutofillProfile&)> on_added,
+      autofill::AutofillProfile* profile = nullptr);
   void EditorViewUpdated();
 
   void ShowCvcUnmaskPrompt(
diff --git a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
index 6f5a599..3e83ac61 100644
--- a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
@@ -526,7 +526,12 @@
 
     case static_cast<int>(
         PaymentSheetViewControllerTags::ADD_CONTACT_INFO_BUTTON):
-      dialog()->ShowContactInfoEditor(BackNavigationType::kPaymentSheet);
+      dialog()->ShowContactInfoEditor(
+          BackNavigationType::kPaymentSheet,
+          /*on_edited=*/base::OnceClosure(),  // This is always an add.
+          /*on_added=*/
+          base::BindOnce(&PaymentRequestState::AddAutofillContactProfile,
+                         base::Unretained(state()), /*selected=*/true));
       break;
 
     case static_cast<int>(
diff --git a/chrome/browser/ui/views/payments/profile_list_view_controller.cc b/chrome/browser/ui/views/payments/profile_list_view_controller.cc
index f30561a6..657d5a6 100644
--- a/chrome/browser/ui/views/payments/profile_list_view_controller.cc
+++ b/chrome/browser/ui/views/payments/profile_list_view_controller.cc
@@ -245,7 +245,15 @@
   }
 
   void ShowEditor(autofill::AutofillProfile* profile) override {
-    dialog()->ShowContactInfoEditor(BackNavigationType::kPaymentSheet, profile);
+    dialog()->ShowContactInfoEditor(
+        BackNavigationType::kPaymentSheet,
+        /*on_edited=*/
+        base::BindOnce(&PaymentRequestState::SetSelectedContactProfile,
+                       base::Unretained(state()), profile),
+        /*on_added=*/
+        base::BindOnce(&PaymentRequestState::AddAutofillContactProfile,
+                       base::Unretained(state()), /*selected=*/true),
+        profile);
   }
 
   autofill::AutofillProfile* GetSelectedProfile() override {
diff --git a/chrome/browser/ui/webui/options/browser_options_handler.cc b/chrome/browser/ui/webui/options/browser_options_handler.cc
index e35875c..cff93a3 100644
--- a/chrome/browser/ui/webui/options/browser_options_handler.cc
+++ b/chrome/browser/ui/webui/options/browser_options_handler.cc
@@ -84,7 +84,7 @@
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/proximity_auth/switches.h"
 #include "components/proxy_config/proxy_config_pref_names.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/search_engines/template_url.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/signin/core/browser/signin_manager.h"
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index 9cd9616..191a685 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -27,7 +27,7 @@
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/google/core/browser/google_util.h"
 #include "components/password_manager/core/browser/password_manager_constants.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
 #include "content/public/browser/web_ui_data_source.h"
diff --git a/chrome/browser/ui/webui/settings/safe_browsing_handler.cc b/chrome/browser/ui/webui/settings/safe_browsing_handler.cc
index 1490781..a77b474 100644
--- a/chrome/browser/ui/webui/settings/safe_browsing_handler.cc
+++ b/chrome/browser/ui/webui/settings/safe_browsing_handler.cc
@@ -8,7 +8,7 @@
 #include "base/bind_helpers.h"
 #include "base/values.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "content/public/browser/web_ui.h"
 
 namespace settings {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 42efbabfc..028825f 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1627,6 +1627,7 @@
       "../browser/ui/toolbar/mock_component_toolbar_actions_factory.cc",
       "../browser/ui/toolbar/mock_component_toolbar_actions_factory.h",
       "../browser/ui/update_chrome_dialog_browsertest.cc",
+      "../browser/ui/views/chrome_cleaner_dialog_browsertest_win.cc",
       "../browser/ui/views/hung_renderer_view_browsertest.cc",
       "../browser/ui/webui/bidi_checker_web_ui_test.cc",
       "../browser/ui/webui/bidi_checker_web_ui_test.h",
@@ -2424,7 +2425,6 @@
     if (is_win) {
       sources += [
         "../browser/extensions/api/networking_private/networking_private_credentials_getter_browsertest.cc",
-        "../browser/ui/views/chrome_cleaner_dialog_browsertest.cc",
         "../browser/ui/views/settings_reset_prompt_dialog_browsertest.cc",
       ]
     }
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/notifications/MockNotificationManagerProxy.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/notifications/MockNotificationManagerProxy.java
index 25e43b42..06de368 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/notifications/MockNotificationManagerProxy.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/notifications/MockNotificationManagerProxy.java
@@ -7,13 +7,16 @@
 import android.app.Notification;
 
 import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
+import org.chromium.chrome.browser.notifications.channels.Channel;
 import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nullable;
 
@@ -23,9 +26,8 @@
  */
 public class MockNotificationManagerProxy implements NotificationManagerProxy {
     private static final String KEY_SEPARATOR = ":";
-    private List<ChannelDefinitions.Channel> mChannels;
-    private List<ChannelDefinitions.ChannelGroup> mNotificationChannelGroups;
-
+    private List<Channel> mChannels;
+    private Set<ChannelDefinitions.ChannelGroup> mNotificationChannelGroups;
     /**
      * Holds a notification and the arguments passed to #notify and #cancel.
      */
@@ -50,7 +52,7 @@
         mNotifications = new LinkedHashMap<>();
         mMutationCount = 0;
         mChannels = new ArrayList<>();
-        mNotificationChannelGroups = new ArrayList<>();
+        mNotificationChannelGroups = new HashSet<>();
     }
 
     /**
@@ -61,7 +63,7 @@
      * @return List of the managed notifications.
      */
     public List<NotificationEntry> getNotifications() {
-        return new ArrayList<NotificationEntry>(mNotifications.values());
+        return new ArrayList<>(mNotifications.values());
     }
 
     /**
@@ -97,37 +99,29 @@
     }
 
     @Override
-    public void createNotificationChannel(ChannelDefinitions.Channel channel) {
+    public void createNotificationChannel(Channel channel) {
         mChannels.add(channel);
     }
 
-    public List<ChannelDefinitions.Channel> getChannels() {
-        return mChannels;
-    }
-
     @Override
     public void createNotificationChannelGroup(ChannelDefinitions.ChannelGroup channelGroup) {
         mNotificationChannelGroups.add(channelGroup);
     }
 
+    @Override
+    public List<Channel> getNotificationChannels() {
+        return mChannels;
+    }
+
     public List<ChannelDefinitions.ChannelGroup> getNotificationChannelGroups() {
-        return mNotificationChannelGroups;
+        return new ArrayList<>(mNotificationChannelGroups);
     }
 
     @Override
-    public List<String> getNotificationChannelIds() {
-        List<String> channelIds = new ArrayList<>();
-        for (ChannelDefinitions.Channel channel : mChannels) {
-            channelIds.add(channel.mId);
-        }
-        return channelIds;
-    }
-
-    @Override
-    public void deleteNotificationChannel(@ChannelDefinitions.ChannelId String id) {
-        for (Iterator<ChannelDefinitions.Channel> it = mChannels.iterator(); it.hasNext();) {
-            ChannelDefinitions.Channel channel = it.next();
-            if (id.equals(channel.mId)) it.remove();
+    public void deleteNotificationChannel(String id) {
+        for (Iterator<Channel> it = mChannels.iterator(); it.hasNext();) {
+            Channel channel = it.next();
+            if (id.equals(channel.getId())) it.remove();
         }
     }
 
diff --git a/chrome/test/base/testing_io_thread_state.cc b/chrome/test/base/testing_io_thread_state.cc
index 8e6ab86..c3c1205c 100644
--- a/chrome/test/base/testing_io_thread_state.cc
+++ b/chrome/test/base/testing_io_thread_state.cc
@@ -96,8 +96,7 @@
 void TestingIOThreadState::Shutdown(const base::Closure& done) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
-  delete io_thread_state_->globals();
-  io_thread_state_->SetGlobalsForTesting(NULL);
+  io_thread_state_->CleanUp();
   done.Run();
 }
 
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 820846ac..987c03e 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -209,6 +209,7 @@
       "//components/policy/core/browser:unit_tests",
       "//components/policy/core/common:unit_tests",
       "//components/precache/content:unit_tests",
+      "//components/safe_browsing/common:unit_tests",
       "//components/safe_browsing/password_protection:password_protection_unittest",
       "//components/safe_browsing_db:unit_tests",
       "//components/safe_json:unit_tests",
diff --git a/components/handoff/handoff_manager.mm b/components/handoff/handoff_manager.mm
index 8300210..cdcb8f1 100644
--- a/components/handoff/handoff_manager.mm
+++ b/components/handoff/handoff_manager.mm
@@ -5,7 +5,7 @@
 #include "components/handoff/handoff_manager.h"
 
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_nsobject.h"
 #include "net/base/mac/url_conversions.h"
 
@@ -34,6 +34,7 @@
 @end
 
 @implementation HandoffManager {
+  base::mac::ObjCPropertyReleaser _propertyReleaser_HandoffManager;
   GURL _activeURL;
   NSUserActivity* _userActivity;
   handoff::Origin _origin;
@@ -52,6 +53,7 @@
 - (instancetype)init {
   self = [super init];
   if (self) {
+    _propertyReleaser_HandoffManager.Init(self, [HandoffManager class]);
 #if defined(OS_MACOSX) && !defined(OS_IOS)
     _origin = handoff::ORIGIN_MAC;
 #elif defined(OS_IOS)
@@ -63,11 +65,6 @@
   return self;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (void)updateActiveURL:(const GURL&)url {
 #if defined(OS_MACOSX) && !defined(OS_IOS)
   // Handoff is only available on OSX 10.10+.
diff --git a/components/offline_pages/core/offline_page_model_query.cc b/components/offline_pages/core/offline_page_model_query.cc
index 5fff06f..382e20b 100644
--- a/components/offline_pages/core/offline_page_model_query.cc
+++ b/components/offline_pages/core/offline_page_model_query.cc
@@ -68,6 +68,12 @@
   return *this;
 }
 
+OfflinePageModelQueryBuilder& OfflinePageModelQueryBuilder::RequireNamespace(
+    const std::string& name_space) {
+  name_space_ = base::MakeUnique<std::string>(name_space);
+  return *this;
+}
+
 std::unique_ptr<OfflinePageModelQuery> OfflinePageModelQueryBuilder::Build(
     ClientPolicyController* controller) {
   DCHECK(controller);
@@ -89,7 +95,15 @@
   std::vector<std::string> allowed_namespaces;
   bool uses_namespace_restrictions = false;
 
-  for (auto& name_space : controller->GetAllNamespaces()) {
+  std::vector<std::string> namespaces;
+  if (name_space_) {
+    uses_namespace_restrictions = true;
+    namespaces.push_back(*name_space_);
+  } else {
+    namespaces = controller->GetAllNamespaces();
+  }
+
+  for (auto& name_space : namespaces) {
     // If any exclusion requirements exist, and the namespace matches one of
     // those excluded by policy, skip adding it to |allowed_namespaces|.
     if ((removed_on_cache_reset_ == Requirement::EXCLUDE_MATCHING &&
diff --git a/components/offline_pages/core/offline_page_model_query.h b/components/offline_pages/core/offline_page_model_query.h
index ae79913a..4107b39 100644
--- a/components/offline_pages/core/offline_page_model_query.h
+++ b/components/offline_pages/core/offline_page_model_query.h
@@ -113,6 +113,9 @@
   OfflinePageModelQueryBuilder& RequireRestrictedToOriginalTab(
       Requirement original_tab);
 
+  // Only include results from a single namespace.
+  OfflinePageModelQueryBuilder& RequireNamespace(const std::string& name_space);
+
   // Builds the query using the namespace policies provided by |controller|
   // This resets the internal state.  |controller| should not be |nullptr|.
   std::unique_ptr<OfflinePageModelQuery> Build(
@@ -135,6 +138,8 @@
   Requirement shown_as_recently_visited_site_ = Requirement::UNSET;
   Requirement restricted_to_original_tab_ = Requirement::UNSET;
 
+  std::unique_ptr<std::string> name_space_;
+
   DISALLOW_COPY_AND_ASSIGN(OfflinePageModelQueryBuilder);
 };
 
diff --git a/components/offline_pages/core/offline_page_model_query_unittest.cc b/components/offline_pages/core/offline_page_model_query_unittest.cc
index fad1c62..01eb27b6a 100644
--- a/components/offline_pages/core/offline_page_model_query_unittest.cc
+++ b/components/offline_pages/core/offline_page_model_query_unittest.cc
@@ -437,4 +437,19 @@
   EXPECT_FALSE(query->Matches(recent_page()));
 }
 
+TEST_F(OfflinePageModelQueryTest, RequireNamespace) {
+  builder_.RequireNamespace(kDefaultNamespace);
+  std::unique_ptr<OfflinePageModelQuery> query = builder_.Build(&policy_);
+  auto restriction = query->GetRestrictedToNamespaces();
+  std::set<std::string> namespaces_allowed = restriction.second;
+  bool restricted_to_namespaces = restriction.first;
+  EXPECT_TRUE(restricted_to_namespaces);
+  EXPECT_EQ(1U, namespaces_allowed.size());
+  EXPECT_TRUE(namespaces_allowed.find(kDefaultNamespace) !=
+              namespaces_allowed.end());
+
+  EXPECT_TRUE(query->Matches(kTestItem1));
+  EXPECT_FALSE(query->Matches(test_namespace_page()));
+}
+
 }  // namespace offline_pages
diff --git a/components/payments/content/payment_request_state.cc b/components/payments/content/payment_request_state.cc
index fb728d1..1236b70 100644
--- a/components/payments/content/payment_request_state.cc
+++ b/components/payments/content/payment_request_state.cc
@@ -166,6 +166,18 @@
     SetSelectedShippingProfile(new_cached_profile);
 }
 
+void PaymentRequestState::AddAutofillContactProfile(
+    bool selected,
+    const autofill::AutofillProfile& profile) {
+  profile_cache_.push_back(
+      base::MakeUnique<autofill::AutofillProfile>(profile));
+  autofill::AutofillProfile* new_cached_profile = profile_cache_.back().get();
+  contact_profiles_.push_back(new_cached_profile);
+
+  if (selected)
+    SetSelectedContactProfile(new_cached_profile);
+}
+
 void PaymentRequestState::SetSelectedShippingOption(
     const std::string& shipping_option_id) {
   spec_->StartWaitingForUpdateWith(
diff --git a/components/payments/content/payment_request_state.h b/components/payments/content/payment_request_state.h
index 66241bd..72ea70ac5a 100644
--- a/components/payments/content/payment_request_state.h
+++ b/components/payments/content/payment_request_state.h
@@ -149,6 +149,12 @@
   void AddAutofillShippingProfile(bool selected,
                                   const autofill::AutofillProfile& profile);
 
+  // Creates and adds an AutofillProfile as a contact profile, which makes a
+  // copy of |profile|. |selected| indicates if the newly-created shipping
+  // profile should be selected, after which observers will be notified.
+  void AddAutofillContactProfile(bool selected,
+                                 const autofill::AutofillProfile& profile);
+
   // Setters to change the selected information. Will have the side effect of
   // recomputing "is ready to pay" and notify observers.
   void SetSelectedShippingOption(const std::string& shipping_option_id);
diff --git a/components/prefs/json_pref_store.cc b/components/prefs/json_pref_store.cc
index cf4d2a3..7716082 100644
--- a/components/prefs/json_pref_store.cc
+++ b/components/prefs/json_pref_store.cc
@@ -143,13 +143,13 @@
 
 JsonPrefStore::JsonPrefStore(
     const base::FilePath& pref_filename,
-    const scoped_refptr<base::SequencedTaskRunner>& sequenced_task_runner,
+    scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner,
     std::unique_ptr<PrefFilter> pref_filter)
     : path_(pref_filename),
-      sequenced_task_runner_(sequenced_task_runner),
+      sequenced_task_runner_(std::move(sequenced_task_runner)),
       prefs_(new base::DictionaryValue()),
       read_only_(false),
-      writer_(pref_filename, sequenced_task_runner),
+      writer_(pref_filename, sequenced_task_runner_),
       pref_filter_(std::move(pref_filter)),
       initialized_(false),
       filtering_in_progress_(false),
diff --git a/components/prefs/json_pref_store.h b/components/prefs/json_pref_store.h
index 6284b8d5..b298d30 100644
--- a/components/prefs/json_pref_store.h
+++ b/components/prefs/json_pref_store.h
@@ -19,9 +19,11 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/non_thread_safe.h"
 #include "components/prefs/base_prefs_export.h"
 #include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_filter.h"
 
 class PrefFilter;
 
@@ -62,13 +64,18 @@
       const base::FilePath& pref_filename,
       base::SequencedWorkerPool* worker_pool);
 
-  // |sequenced_task_runner| must be a shutdown-blocking task runner, ideally
-  // created by the GetTaskRunnerForFile() method above.
-  // |pref_filename| is the path to the file to read prefs from.
-  JsonPrefStore(
-      const base::FilePath& pref_filename,
-      const scoped_refptr<base::SequencedTaskRunner>& sequenced_task_runner,
-      std::unique_ptr<PrefFilter> pref_filter);
+  // |pref_filename| is the path to the file to read prefs from. It is incorrect
+  // to create multiple JsonPrefStore with the same |pref_filename|.
+  // |sequenced_task_runner| is used for asynchronous reads and writes. It must
+  // have the base::TaskShutdownBehavior::BLOCK_SHUTDOWN and base::MayBlock()
+  // traits. Unless external tasks need to run on the same sequence as
+  // JsonPrefStore tasks, keep the default value.
+  JsonPrefStore(const base::FilePath& pref_filename,
+                scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner =
+                    base::CreateSequencedTaskRunnerWithTraits(
+                        {base::MayBlock(),
+                         base::TaskShutdownBehavior::BLOCK_SHUTDOWN}),
+                std::unique_ptr<PrefFilter> pref_filter = nullptr);
 
   // PrefStore overrides:
   bool GetValue(const std::string& key,
diff --git a/components/proxy_config/pref_proxy_config_tracker.h b/components/proxy_config/pref_proxy_config_tracker.h
index d51abc8a..77e2e36c 100644
--- a/components/proxy_config/pref_proxy_config_tracker.h
+++ b/components/proxy_config/pref_proxy_config_tracker.h
@@ -29,7 +29,7 @@
   // before DetachFromPrefService was called. Takes ownership of the passed
   // |base_service|, which can be NULL. This |base_service| provides the proxy
   // settings of the OS (except of ChromeOS). This must be called on the
-  // UI thread.
+  // UI thread.  May only be called once on a PrefProxyConfigTracker.
   virtual std::unique_ptr<net::ProxyConfigService>
   CreateTrackingProxyConfigService(
       std::unique_ptr<net::ProxyConfigService> base_service) = 0;
diff --git a/components/proxy_config/pref_proxy_config_tracker_impl.cc b/components/proxy_config/pref_proxy_config_tracker_impl.cc
index f8077aa..0fead147 100644
--- a/components/proxy_config/pref_proxy_config_tracker_impl.cc
+++ b/components/proxy_config/pref_proxy_config_tracker_impl.cc
@@ -24,10 +24,12 @@
 //============================= ProxyConfigServiceImpl =======================
 
 ProxyConfigServiceImpl::ProxyConfigServiceImpl(
-    net::ProxyConfigService* base_service)
-    : base_service_(base_service),
-      pref_config_state_(ProxyPrefs::CONFIG_UNSET),
-      pref_config_read_pending_(true),
+    std::unique_ptr<net::ProxyConfigService> base_service,
+    ProxyPrefs::ConfigState initial_config_state,
+    const net::ProxyConfig& initial_config)
+    : base_service_(std::move(base_service)),
+      pref_config_state_(initial_config_state),
+      pref_config_(initial_config),
       registered_observer_(false) {
   // ProxyConfigServiceImpl is created on the UI thread, but used on the network
   // thread.
@@ -54,9 +56,6 @@
 ProxyConfigServiceImpl::GetLatestProxyConfig(net::ProxyConfig* config) {
   RegisterObserver();
 
-  if (pref_config_read_pending_)
-    return net::ProxyConfigService::CONFIG_PENDING;
-
   // Ask the base service if available.
   net::ProxyConfig system_config;
   ConfigAvailability system_availability =
@@ -79,7 +78,6 @@
     ProxyPrefs::ConfigState config_state,
     const net::ProxyConfig& config) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  pref_config_read_pending_ = false;
   pref_config_state_ = config_state;
   pref_config_ = config;
 
@@ -109,7 +107,7 @@
 
   // Check whether there is a proxy configuration defined by preferences. In
   // this case that proxy configuration takes precedence and the change event
-  // from the delegate proxy service can be disregarded.
+  // from the delegate proxy config service can be disregarded.
   if (!PrefProxyConfigTrackerImpl::PrefPrecedes(pref_config_state_)) {
     net::ProxyConfig actual_config;
     availability = GetLatestProxyConfig(&actual_config);
@@ -149,12 +147,12 @@
 std::unique_ptr<net::ProxyConfigService>
 PrefProxyConfigTrackerImpl::CreateTrackingProxyConfigService(
     std::unique_ptr<net::ProxyConfigService> base_service) {
-  proxy_config_service_impl_ =
-      new ProxyConfigServiceImpl(base_service.release());
+  DCHECK(!proxy_config_service_impl_);
+  proxy_config_service_impl_ = new ProxyConfigServiceImpl(
+      std::move(base_service), config_state_, pref_config_);
   VLOG(1) << this << ": set chrome proxy config service to "
           << proxy_config_service_impl_;
-  if (proxy_config_service_impl_ && update_pending_)
-    OnProxyConfigChanged(config_state_, pref_config_);
+  update_pending_ = false;
 
   return std::unique_ptr<net::ProxyConfigService>(proxy_config_service_impl_);
 }
diff --git a/components/proxy_config/pref_proxy_config_tracker_impl.h b/components/proxy_config/pref_proxy_config_tracker_impl.h
index ec42a7922..d8a22f6 100644
--- a/components/proxy_config/pref_proxy_config_tracker_impl.h
+++ b/components/proxy_config/pref_proxy_config_tracker_impl.h
@@ -36,10 +36,9 @@
 class ProxyConfigServiceImpl : public net::ProxyConfigService,
                                public net::ProxyConfigService::Observer {
  public:
-  // Takes ownership of the passed |base_service|.
-  // GetLatestProxyConfig returns ConfigAvailability::CONFIG_PENDING until
-  // UpdateProxyConfig has been called.
-  explicit ProxyConfigServiceImpl(net::ProxyConfigService* base_service);
+  ProxyConfigServiceImpl(std::unique_ptr<net::ProxyConfigService> base_service,
+                         ProxyPrefs::ConfigState initial_config_state,
+                         const net::ProxyConfig& initial_config);
   ~ProxyConfigServiceImpl() override;
 
   // ProxyConfigService implementation:
@@ -71,10 +70,6 @@
   // Configuration as defined by prefs.
   net::ProxyConfig pref_config_;
 
-  // Flag that indicates that a PrefProxyConfigTracker needs to inform us
-  // about a proxy configuration before we may return any configuration.
-  bool pref_config_read_pending_;
-
   // Indicates whether the base service registration is done.
   bool registered_observer_;
 
diff --git a/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc b/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc
index b0fd11ed..b0fd3b5 100644
--- a/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc
+++ b/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc
@@ -74,22 +74,22 @@
 
 class PrefProxyConfigTrackerImplTest : public testing::Test {
  protected:
-  PrefProxyConfigTrackerImplTest() {
+  PrefProxyConfigTrackerImplTest() {}
+
+  // Initializes the proxy config service. The delegate config service has the
+  // specified initial config availability.
+  void InitConfigService(net::ProxyConfigService::ConfigAvailability
+                             delegate_config_availability) {
     pref_service_.reset(new TestingPrefServiceSimple());
     PrefProxyConfigTrackerImpl::RegisterPrefs(pref_service_->registry());
     fixed_config_.set_pac_url(GURL(kFixedPacUrl));
     delegate_service_ =
-        new TestProxyConfigService(fixed_config_,
-                                   net::ProxyConfigService::CONFIG_VALID);
+        new TestProxyConfigService(fixed_config_, delegate_config_availability);
     proxy_config_tracker_.reset(new PrefProxyConfigTrackerImpl(
         pref_service_.get(), base::ThreadTaskRunnerHandle::Get()));
     proxy_config_service_ =
         proxy_config_tracker_->CreateTrackingProxyConfigService(
             std::unique_ptr<net::ProxyConfigService>(delegate_service_));
-    // SetProxyConfigServiceImpl triggers update of initial prefs proxy
-    // config by tracker to chrome proxy config service, so flush all pending
-    // tasks so that tests start fresh.
-    base::RunLoop().RunUntilIdle();
   }
 
   ~PrefProxyConfigTrackerImplTest() override {
@@ -104,12 +104,11 @@
   TestProxyConfigService* delegate_service_; // weak
   std::unique_ptr<net::ProxyConfigService> proxy_config_service_;
   net::ProxyConfig fixed_config_;
-
- private:
   std::unique_ptr<PrefProxyConfigTrackerImpl> proxy_config_tracker_;
 };
 
 TEST_F(PrefProxyConfigTrackerImplTest, BaseConfiguration) {
+  InitConfigService(net::ProxyConfigService::CONFIG_VALID);
   net::ProxyConfig actual_config;
   EXPECT_EQ(net::ProxyConfigService::CONFIG_VALID,
             proxy_config_service_->GetLatestProxyConfig(&actual_config));
@@ -117,6 +116,7 @@
 }
 
 TEST_F(PrefProxyConfigTrackerImplTest, DynamicPrefOverrides) {
+  InitConfigService(net::ProxyConfigService::CONFIG_VALID);
   pref_service_->SetManagedPref(proxy_config::prefs::kProxy,
                                 ProxyConfigDictionary::CreateFixedServers(
                                     "http://example.com:3128", std::string()));
@@ -149,6 +149,7 @@
 }
 
 TEST_F(PrefProxyConfigTrackerImplTest, Observers) {
+  InitConfigService(net::ProxyConfigService::CONFIG_VALID);
   const net::ProxyConfigService::ConfigAvailability CONFIG_VALID =
       net::ProxyConfigService::CONFIG_VALID;
   MockObserver observer;
@@ -204,6 +205,7 @@
 }
 
 TEST_F(PrefProxyConfigTrackerImplTest, Fallback) {
+  InitConfigService(net::ProxyConfigService::CONFIG_VALID);
   const net::ProxyConfigService::ConfigAvailability CONFIG_VALID =
       net::ProxyConfigService::CONFIG_VALID;
   MockObserver observer;
@@ -257,6 +259,7 @@
 }
 
 TEST_F(PrefProxyConfigTrackerImplTest, ExplicitSystemSettings) {
+  InitConfigService(net::ProxyConfigService::CONFIG_VALID);
   pref_service_->SetRecommendedPref(proxy_config::prefs::kProxy,
                                     ProxyConfigDictionary::CreateAutoDetect());
   pref_service_->SetUserPref(proxy_config::prefs::kProxy,
@@ -270,4 +273,34 @@
   EXPECT_EQ(GURL(kFixedPacUrl), actual_config.pac_url());
 }
 
+// Test the case where the delegate service gets a config only after the service
+// is created.
+TEST_F(PrefProxyConfigTrackerImplTest, DelegateConfigServiceGetsConfigLate) {
+  InitConfigService(net::ProxyConfigService::CONFIG_PENDING);
+
+  testing::StrictMock<MockObserver> observer;
+  proxy_config_service_->AddObserver(&observer);
+
+  net::ProxyConfig actual_config;
+  EXPECT_EQ(net::ProxyConfigService::CONFIG_PENDING,
+            proxy_config_service_->GetLatestProxyConfig(&actual_config));
+
+  // When the delegate service gets the config, the other service should update
+  // its observers.
+  EXPECT_CALL(observer,
+              OnProxyConfigChanged(ProxyConfigMatches(fixed_config_),
+                                   net::ProxyConfigService::CONFIG_VALID))
+      .Times(1);
+  delegate_service_->SetProxyConfig(fixed_config_,
+                                    net::ProxyConfigService::CONFIG_VALID);
+
+  // Since no prefs were set, should just use the delegated config service's
+  // settings.
+  EXPECT_EQ(net::ProxyConfigService::CONFIG_VALID,
+            proxy_config_service_->GetLatestProxyConfig(&actual_config));
+  EXPECT_EQ(GURL(kFixedPacUrl), actual_config.pac_url());
+
+  proxy_config_service_->RemoveObserver(&observer);
+}
+
 }  // namespace
diff --git a/components/safe_browsing/BUILD.gn b/components/safe_browsing/BUILD.gn
index ff80235e..c64922b 100644
--- a/components/safe_browsing/BUILD.gn
+++ b/components/safe_browsing/BUILD.gn
@@ -24,8 +24,8 @@
     ":base_ping_manager",
     "//base:base",
     "//base:i18n",
+    "//components/safe_browsing/common:safe_browsing_prefs",
     "//components/safe_browsing_db:database_manager",
-    "//components/safe_browsing_db:safe_browsing_prefs",
     "//components/security_interstitials/content:security_interstitial_page",
     "//components/security_interstitials/core:core",
     "//components/subresource_filter/content/browser:browser",
diff --git a/components/safe_browsing/base_blocking_page.cc b/components/safe_browsing/base_blocking_page.cc
index 3b047b9..b1b57c6b 100644
--- a/components/safe_browsing/base_blocking_page.cc
+++ b/components/safe_browsing/base_blocking_page.cc
@@ -9,7 +9,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/security_interstitials/content/security_interstitial_controller_client.h"
 #include "components/security_interstitials/core/metrics_helper.h"
 #include "components/security_interstitials/core/safe_browsing_loud_error_ui.h"
diff --git a/components/safe_browsing/common/BUILD.gn b/components/safe_browsing/common/BUILD.gn
index 4e32332..212b21df 100644
--- a/components/safe_browsing/common/BUILD.gn
+++ b/components/safe_browsing/common/BUILD.gn
@@ -20,3 +20,28 @@
     "//url/ipc:url_ipc",
   ]
 }
+
+static_library("safe_browsing_prefs") {
+  sources = [
+    "safe_browsing_prefs.cc",
+    "safe_browsing_prefs.h",
+  ]
+  deps = [
+    "//base:base",
+    "//components/prefs",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "safe_browsing_prefs_unittest.cc",
+  ]
+  deps = [
+    ":safe_browsing_prefs",
+    "//base:base",
+    "//base/test:test_support",
+    "//components/prefs:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/safe_browsing/common/DEPS b/components/safe_browsing/common/DEPS
index acf92ab..61eff427 100644
--- a/components/safe_browsing/common/DEPS
+++ b/components/safe_browsing/common/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/prefs",
   "+ipc",
   "+url"
 ]
diff --git a/components/safe_browsing_db/safe_browsing_prefs.cc b/components/safe_browsing/common/safe_browsing_prefs.cc
similarity index 99%
rename from components/safe_browsing_db/safe_browsing_prefs.cc
rename to components/safe_browsing/common/safe_browsing_prefs.cc
index 3895131c..83f7588 100644
--- a/components/safe_browsing_db/safe_browsing_prefs.cc
+++ b/components/safe_browsing/common/safe_browsing_prefs.cc
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
+
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
 
 namespace {
 
diff --git a/components/safe_browsing_db/safe_browsing_prefs.h b/components/safe_browsing/common/safe_browsing_prefs.h
similarity index 96%
rename from components/safe_browsing_db/safe_browsing_prefs.h
rename to components/safe_browsing/common/safe_browsing_prefs.h
index 54666f3..ec87b39 100644
--- a/components/safe_browsing_db/safe_browsing_prefs.h
+++ b/components/safe_browsing/common/safe_browsing_prefs.h
@@ -4,8 +4,8 @@
 //
 // Safe Browsing preferences and some basic utility functions for using them.
 
-#ifndef COMPONENTS_SAFE_BROWSING_DB_SAFE_BROWSING_PREFS_H_
-#define COMPONENTS_SAFE_BROWSING_DB_SAFE_BROWSING_PREFS_H_
+#ifndef COMPONENTS_SAFE_BROWSING_COMMON_SAFE_BROWSING_PREFS_H_
+#define COMPONENTS_SAFE_BROWSING_COMMON_SAFE_BROWSING_PREFS_H_
 
 #include "base/feature_list.h"
 
@@ -148,4 +148,4 @@
 
 }  // namespace safe_browsing
 
-#endif  // COMPONENTS_SAFE_BROWSING_DB_SAFE_BROWSING_PREFS_H_
+#endif  // COMPONENTS_SAFE_BROWSING_COMMON_SAFE_BROWSING_PREFS_H_
diff --git a/components/safe_browsing_db/safe_browsing_prefs_unittest.cc b/components/safe_browsing/common/safe_browsing_prefs_unittest.cc
similarity index 99%
rename from components/safe_browsing_db/safe_browsing_prefs_unittest.cc
rename to components/safe_browsing/common/safe_browsing_prefs_unittest.cc
index b6b3a08..5d11011 100644
--- a/components/safe_browsing_db/safe_browsing_prefs_unittest.cc
+++ b/components/safe_browsing/common/safe_browsing_prefs_unittest.cc
@@ -11,7 +11,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace safe_browsing {
diff --git a/components/safe_browsing_db/BUILD.gn b/components/safe_browsing_db/BUILD.gn
index 68a841c..fb9286f 100644
--- a/components/safe_browsing_db/BUILD.gn
+++ b/components/safe_browsing_db/BUILD.gn
@@ -31,9 +31,9 @@
     ":database_manager",
     ":hit_report",
     ":prefix_set",
-    ":safe_browsing_prefs",
     ":safebrowsing_proto",
     ":util",
+    "//components/safe_browsing/common:safe_browsing_prefs",
   ]
 }
 
@@ -82,8 +82,8 @@
     ":util",
   ]
   deps = [
-    ":safe_browsing_prefs",
     "//components/metrics",
+    "//components/safe_browsing/common:safe_browsing_prefs",
     "//url",
   ]
 }
@@ -152,17 +152,6 @@
   ]
 }
 
-static_library("safe_browsing_prefs") {
-  sources = [
-    "safe_browsing_prefs.cc",
-    "safe_browsing_prefs.h",
-  ]
-  deps = [
-    "//base:base",
-    "//components/prefs",
-  ]
-}
-
 static_library("test_database_manager") {
   sources = [
     "test_database_manager.cc",
@@ -181,8 +170,8 @@
     "util.h",
   ]
   public_deps = [
-    ":safe_browsing_prefs",
     ":v4_protocol_manager_util",
+    "//components/safe_browsing/common:safe_browsing_prefs",
   ]
   deps = [
     "//base",
@@ -325,12 +314,12 @@
     "v4_update_protocol_manager.h",
   ]
   deps = [
-    ":safe_browsing_prefs",
     ":safebrowsing_proto",
     ":util",
     ":v4_protocol_manager_util",
     "//base",
     "//components/data_use_measurement/core",
+    "//components/safe_browsing/common:safe_browsing_prefs",
     "//net",
     "//url",
   ]
@@ -413,7 +402,6 @@
   sources = [
     "database_manager_unittest.cc",
     "prefix_set_unittest.cc",
-    "safe_browsing_prefs_unittest.cc",
     "util_unittest.cc",
     "v4_database_unittest.cc",
     "v4_get_hash_protocol_manager_unittest.cc",
@@ -426,7 +414,6 @@
   deps = [
     ":database_manager",
     ":prefix_set",
-    ":safe_browsing_prefs",
     ":safebrowsing_proto",
     ":test_database_manager",
     ":util",
@@ -441,6 +428,7 @@
     ":v4_update_protocol_manager",
     "//base",
     "//components/prefs:test_support",
+    "//components/safe_browsing/common:safe_browsing_prefs",
     "//content/test:test_support",
     "//crypto",
     "//net",
diff --git a/components/safe_browsing_db/DEPS b/components/safe_browsing_db/DEPS
index 7fafc80..55dc17a 100644
--- a/components/safe_browsing_db/DEPS
+++ b/components/safe_browsing_db/DEPS
@@ -1,6 +1,6 @@
 include_rules = [
   "+components/data_use_measurement/core",
-  "+components/prefs",
+  "+components/safe_browsing/common/safe_browsing_prefs.h",
   "+components/variations",
   "+components/version_info",
   "+content/public/browser",
diff --git a/components/safe_browsing_db/hit_report.h b/components/safe_browsing_db/hit_report.h
index afa5676..4fecc44c 100644
--- a/components/safe_browsing_db/hit_report.h
+++ b/components/safe_browsing_db/hit_report.h
@@ -7,7 +7,7 @@
 #ifndef COMPONENTS_SAFE_BROWSING_DB_HIT_REPORT_H_
 #define COMPONENTS_SAFE_BROWSING_DB_HIT_REPORT_H_
 
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/util.h"
 #include "url/gurl.h"
 
diff --git a/components/safe_browsing_db/util.h b/components/safe_browsing_db/util.h
index ad6f4e82..dd03f5f 100644
--- a/components/safe_browsing_db/util.h
+++ b/components/safe_browsing_db/util.h
@@ -16,7 +16,7 @@
 
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/v4_protocol_manager_util.h"
 
 class GURL;
diff --git a/components/safe_browsing_db/v4_update_protocol_manager.h b/components/safe_browsing_db/v4_update_protocol_manager.h
index 4405742..5c60a0c3 100644
--- a/components/safe_browsing_db/v4_update_protocol_manager.h
+++ b/components/safe_browsing_db/v4_update_protocol_manager.h
@@ -21,7 +21,7 @@
 #include "base/threading/non_thread_safe.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/safebrowsing.pb.h"
 #include "components/safe_browsing_db/util.h"
 #include "components/safe_browsing_db/v4_protocol_manager_util.h"
diff --git a/components/security_interstitials/content/BUILD.gn b/components/security_interstitials/content/BUILD.gn
index d2252c27..d3e598a 100644
--- a/components/security_interstitials/content/BUILD.gn
+++ b/components/security_interstitials/content/BUILD.gn
@@ -20,8 +20,8 @@
     "//base",
     "//components/prefs:prefs",
     "//components/resources",
+    "//components/safe_browsing/common:safe_browsing_prefs",
     "//components/safe_browsing_db:hit_report",
-    "//components/safe_browsing_db:safe_browsing_prefs",
     "//components/safe_browsing_db:util",
     "//components/security_interstitials/core:core",
     "//content/public/browser",
diff --git a/components/security_interstitials/content/DEPS b/components/security_interstitials/content/DEPS
index 5cb2f21e..ec5cfcd 100644
--- a/components/security_interstitials/content/DEPS
+++ b/components/security_interstitials/content/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "+components/grit/components_resources.h",
   "+components/prefs",
+  "+components/safe_browsing/common",
   "+components/safe_browsing_db",
   "+components/security_interstitials/core",
   "+content/public/browser",
diff --git a/components/security_interstitials/content/security_interstitial_controller_client.cc b/components/security_interstitials/content/security_interstitial_controller_client.cc
index a40f0d43..87d0789 100644
--- a/components/security_interstitials/content/security_interstitial_controller_client.cc
+++ b/components/security_interstitials/content/security_interstitial_controller_client.cc
@@ -7,7 +7,7 @@
 #include <utility>
 
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/security_interstitials/core/metrics_helper.h"
 #include "content/public/browser/interstitial_page.h"
 #include "content/public/browser/web_contents.h"
diff --git a/components/security_interstitials/content/security_interstitial_page.cc b/components/security_interstitials/content/security_interstitial_page.cc
index 3b64147..0e339db6 100644
--- a/components/security_interstitials/content/security_interstitial_page.cc
+++ b/components/security_interstitials/content/security_interstitial_page.cc
@@ -11,7 +11,7 @@
 #include "base/values.h"
 #include "components/grit/components_resources.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing_db/safe_browsing_prefs.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/security_interstitials/content/security_interstitial_controller_client.h"
 #include "components/security_interstitials/core/common_string_util.h"
 #include "content/public/browser/interstitial_page.h"
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc b/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc
index c6052e11..45b15bdc 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc
@@ -9,6 +9,8 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
 #include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_argument.h"
 #include "components/subresource_filter/content/browser/content_activation_list_utils.h"
 #include "components/subresource_filter/content/browser/subresource_filter_client.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
@@ -119,8 +121,17 @@
                          url, config.activation_conditions);
                    });
 
-  if (highest_priority_activated_config ==
-      config_list->configs_by_decreasing_priority().end()) {
+  bool has_activated_config =
+      highest_priority_activated_config !=
+      config_list->configs_by_decreasing_priority().end();
+  TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("loading"),
+               "ContentSubresourceFilterDriverFactory::"
+               "ComputeActivationForMainFrameNavigation",
+               "highest_priority_activated_config",
+               has_activated_config
+                   ? highest_priority_activated_config->ToTracedValue()
+                   : base::MakeUnique<base::trace_event::TracedValue>());
+  if (!has_activated_config) {
     activation_decision_ = ActivationDecision::ACTIVATION_CONDITIONS_NOT_MET;
     activation_options_ = Configuration::ActivationOptions();
     return;
@@ -205,6 +216,7 @@
 
   // Only reset the activation decision reason if we would have activated.
   if (whitelisted && activation_decision_ == ActivationDecision::ACTIVATED) {
+    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"), "ActivationWhitelisted");
     activation_decision_ = ActivationDecision::URL_WHITELISTED;
     activation_options_ = Configuration::ActivationOptions();
   }
diff --git a/components/subresource_filter/core/browser/subresource_filter_features.cc b/components/subresource_filter/core/browser/subresource_filter_features.cc
index 5ce505d..5c2d755f1 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features.cc
+++ b/components/subresource_filter/core/browser/subresource_filter_features.cc
@@ -11,14 +11,14 @@
 #include <tuple>
 #include <utility>
 
-#include "base/json/json_writer.h"
 #include "base/lazy_instance.h"
+#include "base/memory/ptr_util.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/synchronization/lock.h"
-#include "base/values.h"
+#include "base/trace_event/trace_event_argument.h"
 #include "components/variations/variations_associated_data.h"
 
 namespace subresource_filter {
@@ -319,27 +319,25 @@
   return !(*this == rhs);
 }
 
-std::ostream& operator<<(std::ostream& os, const Configuration& config) {
-  base::DictionaryValue dict;
-  dict.SetString("activation_scope",
-                 StreamToString(config.activation_conditions.activation_scope));
-  dict.SetString("activation_list",
-                 StreamToString(config.activation_conditions.activation_list));
-  dict.SetInteger("priority", config.activation_conditions.priority);
-  dict.SetString("activation_level",
-                 StreamToString(config.activation_options.activation_level));
-  dict.SetDouble("performance_measurement_rate",
-                 config.activation_options.performance_measurement_rate);
-  dict.SetBoolean("should_suppress_notifications",
-                  config.activation_options.should_suppress_notifications);
-  dict.SetBoolean("should_whitelist_site_on_reload",
-                  config.activation_options.should_whitelist_site_on_reload);
-  dict.SetString("ruleset_flavor",
-                 StreamToString(config.general_settings.ruleset_flavor));
-  std::string json;
-  base::JSONWriter::WriteWithOptions(
-      dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
-  return os << json;
+std::unique_ptr<base::trace_event::TracedValue> Configuration::ToTracedValue()
+    const {
+  auto value = base::MakeUnique<base::trace_event::TracedValue>();
+  value->SetString("activation_scope",
+                   StreamToString(activation_conditions.activation_scope));
+  value->SetString("activation_list",
+                   StreamToString(activation_conditions.activation_list));
+  value->SetInteger("priority", activation_conditions.priority);
+  value->SetString("activation_level",
+                   StreamToString(activation_options.activation_level));
+  value->SetDouble("performance_measurement_rate",
+                   activation_options.performance_measurement_rate);
+  value->SetBoolean("should_suppress_notifications",
+                    activation_options.should_suppress_notifications);
+  value->SetBoolean("should_whitelist_site_on_reload",
+                    activation_options.should_whitelist_site_on_reload);
+  value->SetString("ruleset_flavor",
+                   StreamToString(general_settings.ruleset_flavor));
+  return value;
 }
 
 // ConfigurationList ----------------------------------------------------------
diff --git a/components/subresource_filter/core/browser/subresource_filter_features.h b/components/subresource_filter/core/browser/subresource_filter_features.h
index e075356..a828a4e 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features.h
+++ b/components/subresource_filter/core/browser/subresource_filter_features.h
@@ -5,7 +5,7 @@
 #ifndef COMPONENTS_SUBRESOURCE_FILTER_CORE_BROWSER_SUBRESOURCE_FILTER_FEATURES_H_
 #define COMPONENTS_SUBRESOURCE_FILTER_CORE_BROWSER_SUBRESOURCE_FILTER_FEATURES_H_
 
-#include <iosfwd>
+#include <memory>
 #include <vector>
 
 #include "base/feature_list.h"
@@ -16,6 +16,12 @@
 #include "components/subresource_filter/core/common/activation_list.h"
 #include "components/subresource_filter/core/common/activation_scope.h"
 
+namespace base {
+namespace trace_event {
+class TracedValue;
+}  // namespace trace_event
+}  // namespace base
+
 namespace subresource_filter {
 
 // Encapsulates a set of parameters that define how the subresource filter
@@ -102,6 +108,8 @@
   bool operator==(const Configuration& rhs) const;
   bool operator!=(const Configuration& rhs) const;
 
+  std::unique_ptr<base::trace_event::TracedValue> ToTracedValue() const;
+
   // Factory methods for preset configurations.
   //
   // To add a new preset:
@@ -117,9 +125,6 @@
   GeneralSettings general_settings;
 };
 
-// For logging in tests.
-std::ostream& operator<<(std::ostream& os, const Configuration& config);
-
 // Thread-safe, ref-counted wrapper around an immutable list of configurations.
 class ConfigurationList : public base::RefCountedThreadSafe<ConfigurationList> {
  public:
diff --git a/components/subresource_filter/core/browser/subresource_filter_features_test_support.cc b/components/subresource_filter/core/browser/subresource_filter_features_test_support.cc
index df18a73..dcd7c31 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features_test_support.cc
+++ b/components/subresource_filter/core/browser/subresource_filter_features_test_support.cc
@@ -4,11 +4,15 @@
 
 #include "components/subresource_filter/core/browser/subresource_filter_features_test_support.h"
 
+#include <ostream>
 #include <utility>
 
+#include "base/json/json_writer.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/strings/string_util.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "base/values.h"
 
 namespace subresource_filter {
 namespace testing {
@@ -83,5 +87,15 @@
 
 ScopedSubresourceFilterFeatureToggle::~ScopedSubresourceFilterFeatureToggle() {}
 
+std::ostream& operator<<(std::ostream& os, const Configuration& config) {
+  std::unique_ptr<base::Value> value = config.ToTracedValue()->ToBaseValue();
+  base::DictionaryValue* dict;
+  value->GetAsDictionary(&dict);
+  std::string json;
+  base::JSONWriter::WriteWithOptions(
+      *dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+  return os << json;
+}
+
 }  // namespace testing
 }  // namespace subresource_filter
diff --git a/components/subresource_filter/core/browser/subresource_filter_features_test_support.h b/components/subresource_filter/core/browser/subresource_filter_features_test_support.h
index 974e97b..91ad516 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features_test_support.h
+++ b/components/subresource_filter/core/browser/subresource_filter_features_test_support.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_SUBRESOURCE_FILTER_CORE_BROWSER_SUBRESOURCE_FILTER_FEATURES_TEST_SUPPORT_H_
 #define COMPONENTS_SUBRESOURCE_FILTER_CORE_BROWSER_SUBRESOURCE_FILTER_FEATURES_TEST_SUPPORT_H_
 
+#include <iosfwd>
 #include <memory>
 #include <string>
 
@@ -69,6 +70,9 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedSubresourceFilterFeatureToggle);
 };
 
+// For logging in tests.
+std::ostream& operator<<(std::ostream& os, const Configuration& config);
+
 }  // namespace testing
 }  // namespace subresource_filter
 
diff --git a/content/browser/frame_host/render_widget_host_view_child_frame.cc b/content/browser/frame_host/render_widget_host_view_child_frame.cc
index 5fdd44e..d5500ab4 100644
--- a/content/browser/frame_host/render_widget_host_view_child_frame.cc
+++ b/content/browser/frame_host/render_widget_host_view_child_frame.cc
@@ -475,8 +475,9 @@
 }
 
 void RenderWidgetHostViewChildFrame::ProcessKeyboardEvent(
-    const NativeWebKeyboardEvent& event) {
-  host_->ForwardKeyboardEvent(event);
+    const NativeWebKeyboardEvent& event,
+    const ui::LatencyInfo& latency) {
+  host_->ForwardKeyboardEventWithLatencyInfo(event, latency);
 }
 
 void RenderWidgetHostViewChildFrame::ProcessMouseEvent(
@@ -488,7 +489,11 @@
 void RenderWidgetHostViewChildFrame::ProcessMouseWheelEvent(
     const blink::WebMouseWheelEvent& event,
     const ui::LatencyInfo& latency) {
-  if (event.delta_x != 0 || event.delta_y != 0)
+  if (event.delta_x != 0 || event.delta_y != 0 ||
+      event.phase == blink::WebMouseWheelEvent::kPhaseEnded ||
+      event.phase == blink::WebMouseWheelEvent::kPhaseCancelled ||
+      event.momentum_phase == blink::WebMouseWheelEvent::kPhaseEnded ||
+      event.momentum_phase == blink::WebMouseWheelEvent::kPhaseCancelled)
     host_->ForwardWheelEventWithLatencyInfo(event, latency);
 }
 
diff --git a/content/browser/frame_host/render_widget_host_view_child_frame.h b/content/browser/frame_host/render_widget_host_view_child_frame.h
index 213581d2..df99f2de 100644
--- a/content/browser/frame_host/render_widget_host_view_child_frame.h
+++ b/content/browser/frame_host/render_widget_host_view_child_frame.h
@@ -123,7 +123,8 @@
   bool LockMouse() override;
   void UnlockMouse() override;
   cc::FrameSinkId GetFrameSinkId() override;
-  void ProcessKeyboardEvent(const NativeWebKeyboardEvent& event) override;
+  void ProcessKeyboardEvent(const NativeWebKeyboardEvent& event,
+                            const ui::LatencyInfo& latency) override;
   void ProcessMouseEvent(const blink::WebMouseEvent& event,
                          const ui::LatencyInfo& latency) override;
   void ProcessMouseWheelEvent(const blink::WebMouseWheelEvent& event,
diff --git a/content/browser/indexed_db/leveldb/leveldb_database.cc b/content/browser/indexed_db/leveldb/leveldb_database.cc
index f157f5e..ca777c7 100644
--- a/content/browser/indexed_db/leveldb/leveldb_database.cc
+++ b/content/browser/indexed_db/leveldb/leveldb_database.cc
@@ -481,6 +481,14 @@
       "leveldb/index_db/0x%" PRIXPTR, reinterpret_cast<uintptr_t>(db_.get())));
   dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
                   base::trace_event::MemoryAllocatorDump::kUnitsBytes, size);
+
+  // Dumps in BACKGROUND mode cannot have strings or edges in order to minimize
+  // trace size and instrumentation overhead.
+  if (args.level_of_detail ==
+      base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND) {
+    return true;
+  }
+
   dump->AddString("file_name", "", file_name_for_tracing);
 
   // Memory is allocated from system allocator (malloc).
diff --git a/content/browser/loader/navigation_url_loader_network_service.cc b/content/browser/loader/navigation_url_loader_network_service.cc
index 5ea32da..ccb4290 100644
--- a/content/browser/loader/navigation_url_loader_network_service.cc
+++ b/content/browser/loader/navigation_url_loader_network_service.cc
@@ -18,6 +18,11 @@
 #include "content/browser/loader/navigation_resource_handler.h"
 #include "content/browser/loader/navigation_resource_throttle.h"
 #include "content/browser/loader/navigation_url_loader_delegate.h"
+#include "content/browser/resource_context_impl.h"
+#include "content/browser/service_worker/service_worker_navigation_handle.h"
+#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
+#include "content/browser/service_worker/service_worker_request_handler.h"
+#include "content/browser/web_contents/web_contents_impl.h"
 #include "content/browser/webui/url_data_manager_backend.h"
 #include "content/browser/webui/web_ui_url_loader_factory.h"
 #include "content/public/browser/browser_thread.h"
@@ -39,37 +44,103 @@
 namespace {
 
 static base::LazyInstance<mojom::URLLoaderFactoryPtr>::Leaky
-    g_url_loader_factory = LAZY_INSTANCE_INITIALIZER;
+    g_url_loader_factory;
+#if DCHECK_IS_ON()
+static bool g_url_loader_factory_retrieved;
+#endif  // DCHECK_IS_ON()
 
-using CompleteNavigationStartCallback =
-    base::Callback<void(mojom::URLLoaderFactoryPtrInfo,
-                        std::unique_ptr<ResourceRequest>)>;
+WebContents* GetWebContentsFromFrameTreeNodeID(int frame_tree_node_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  FrameTreeNode* frame_tree_node =
+      FrameTreeNode::GloballyFindByID(frame_tree_node_id);
+  if (!frame_tree_node)
+    return nullptr;
 
-void PrepareNavigationOnIOThread(
+  return WebContentsImpl::FromFrameTreeNode(frame_tree_node);
+}
+
+void PrepareNavigationStartOnIO(
     std::unique_ptr<ResourceRequest> resource_request,
     ResourceContext* resource_context,
-    ResourceType resource_type,
+    ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
     AppCacheNavigationHandleCore* appcache_handle_core,
-    CompleteNavigationStartCallback complete_request) {
-  if (resource_request->request_body.get()) {
+    NavigationRequestInfo* request_info,
+    mojom::URLLoaderFactoryPtrInfo factory_from_ui,
+    const base::Callback<WebContents*(void)>& web_contents_getter,
+    mojom::URLLoaderAssociatedRequest url_loader_request,
+    mojom::URLLoaderClientPtr url_loader_client_to_pass,
+    std::unique_ptr<service_manager::Connector> connector) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  const ResourceType resource_type = request_info->is_main_frame
+                                         ? RESOURCE_TYPE_MAIN_FRAME
+                                         : RESOURCE_TYPE_SUB_FRAME;
+
+  if (resource_request->request_body) {
     AttachRequestBodyBlobDataHandles(resource_request->request_body.get(),
                                      resource_context);
   }
 
-  mojom::URLLoaderFactoryPtrInfo url_loader_factory_ptr_info;
+  mojom::URLLoaderFactoryPtr url_loader_factory_ptr;
+  if (service_worker_navigation_handle_core) {
+    RequestContextFrameType frame_type =
+        request_info->is_main_frame ? REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL
+                                    : REQUEST_CONTEXT_FRAME_TYPE_NESTED;
 
-  if (appcache_handle_core) {
+    storage::BlobStorageContext* blob_storage_context = GetBlobStorageContext(
+        GetChromeBlobStorageContextForResourceContext(resource_context));
+    url_loader_factory_ptr =
+        ServiceWorkerRequestHandler::InitializeForNavigationNetworkService(
+            *resource_request, resource_context,
+            service_worker_navigation_handle_core, blob_storage_context,
+            request_info->begin_params.skip_service_worker, resource_type,
+            request_info->begin_params.request_context_type, frame_type,
+            request_info->are_ancestors_secure,
+            request_info->common_params.post_data, web_contents_getter);
+  }
+
+  // TODO(scottmg): We need to rework AppCache to have it return a
+  // URLLoaderFactoryPtr[Info] here. We should also try to have it return
+  // synchronously in as many cases as possible (especially when there's no
+  // AppCache) to simplify and speed the common case.
+  if (false /*appcache_handle_core*/) {
     AppCacheRequestHandler::InitializeForNavigationNetworkService(
         std::move(resource_request), resource_context, appcache_handle_core,
-        resource_type, complete_request);
+        resource_type,
+        base::Callback<void(
+            mojom::URLLoaderFactoryPtrInfo,
+            std::unique_ptr<ResourceRequest>)>() /* TODO(ananta) */);
     return;
   }
 
-  BrowserThread::PostTask(
-      BrowserThread::UI, FROM_HERE,
-      base::Bind(complete_request,
-                 base::Passed(std::move(url_loader_factory_ptr_info)),
-                 base::Passed(std::move(resource_request))));
+  // If we haven't gotten one from the above, then use the one the UI thread
+  // gave us, or otherwise fallback to the default.
+  mojom::URLLoaderFactory* factory;
+  if (url_loader_factory_ptr) {
+    factory = url_loader_factory_ptr.get();
+  } else {
+    if (factory_from_ui.is_valid()) {
+      url_loader_factory_ptr.Bind(std::move(factory_from_ui));
+      factory = url_loader_factory_ptr.get();
+    } else {
+      if (!g_url_loader_factory.Get()) {
+#if DCHECK_IS_ON()
+        DCHECK(!g_url_loader_factory_retrieved);
+#endif  // DCHECK_IS_ON()
+        connector->BindInterface(mojom::kNetworkServiceName,
+                                 &g_url_loader_factory.Get());
+#if DCHECK_IS_ON()
+        g_url_loader_factory_retrieved = true;
+#endif  // DCHECK_IS_ON()
+      }
+      factory = g_url_loader_factory.Get().get();
+    }
+  }
+
+  factory->CreateLoaderAndStart(
+      std::move(url_loader_request), 0 /* routing_id? */, 0 /* request_id? */,
+      mojom::kURLLoadOptionSendSSLInfo, *resource_request,
+      std::move(url_loader_client_to_pass));
 }
 
 }  // namespace
@@ -79,13 +150,12 @@
     StoragePartition* storage_partition,
     std::unique_ptr<NavigationRequestInfo> request_info,
     std::unique_ptr<NavigationUIData> navigation_ui_data,
-    ServiceWorkerNavigationHandle* service_worker_handle,
+    ServiceWorkerNavigationHandle* service_worker_navigation_handle,
     AppCacheNavigationHandle* appcache_handle,
     NavigationURLLoaderDelegate* delegate)
     : delegate_(delegate),
       binding_(this),
-      request_info_(std::move(request_info)),
-      weak_factory_(this) {
+      request_info_(std::move(request_info)) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP1(
@@ -123,24 +193,38 @@
 
   new_request->request_body = request_info_->common_params.post_data.get();
 
-  // AppCache or post data needs some handling on the IO thread.
-  // The request body may need blob handles to be added to it. This
-  // functionality has to be invoked on the IO thread.
-  if (/*appcache_handle || */ new_request->request_body.get()) {
-    ResourceType resource_type = request_info_->is_main_frame
-                                     ? RESOURCE_TYPE_MAIN_FRAME
-                                     : RESOURCE_TYPE_SUB_FRAME;
-    BrowserThread::PostTask(
-        BrowserThread::IO, FROM_HERE,
-        base::Bind(
-            &PrepareNavigationOnIOThread, base::Passed(std::move(new_request)),
-            resource_context, resource_type,
-            appcache_handle ? appcache_handle->core() : nullptr,
-            base::Bind(&NavigationURLLoaderNetworkService::StartURLRequest,
-                       weak_factory_.GetWeakPtr())));
-    return;
+  mojom::URLLoaderAssociatedRequest loader_associated_request =
+      mojo::MakeRequest(&url_loader_associated_ptr_);
+  mojom::URLLoaderClientPtr url_loader_client_ptr_to_pass;
+  binding_.Bind(mojo::MakeRequest(&url_loader_client_ptr_to_pass));
+
+  // Check if a web UI scheme wants to handle this request.
+  mojom::URLLoaderFactoryPtrInfo factory_ptr_info;
+
+  const auto& schemes = URLDataManagerBackend::GetWebUISchemes();
+  if (std::find(schemes.begin(), schemes.end(), new_request->url.scheme()) !=
+      schemes.end()) {
+    FrameTreeNode* frame_tree_node =
+        FrameTreeNode::GloballyFindByID(request_info_->frame_tree_node_id);
+    factory_ptr_info = GetWebUIURLLoader(frame_tree_node).PassInterface();
   }
-  StartURLRequest(mojom::URLLoaderFactoryPtrInfo(), std::move(new_request));
+
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::Bind(&PrepareNavigationStartOnIO,
+                 base::Passed(std::move(new_request)), resource_context,
+                 service_worker_navigation_handle
+                     ? service_worker_navigation_handle->core()
+                     : nullptr,
+                 appcache_handle ? appcache_handle->core() : nullptr,
+                 request_info_.get(), base::Passed(std::move(factory_ptr_info)),
+                 base::Bind(&GetWebContentsFromFrameTreeNodeID,
+                            request_info_->frame_tree_node_id),
+                 base::Passed(std::move(loader_associated_request)),
+                 base::Passed(std::move(url_loader_client_ptr_to_pass)),
+                 base::Passed(ServiceManagerConnection::GetForProcess()
+                                  ->GetConnector()
+                                  ->Clone())));
 }
 
 NavigationURLLoaderNetworkService::~NavigationURLLoaderNetworkService() {}
@@ -222,63 +306,4 @@
   }
 }
 
-void NavigationURLLoaderNetworkService::StartURLRequest(
-    mojom::URLLoaderFactoryPtrInfo url_loader_factory_info,
-    std::unique_ptr<ResourceRequest> request) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  // Bind the URLClient implementation to this object to pass to the URLLoader.
-  if (binding_.is_bound())
-    binding_.Unbind();
-
-  mojom::URLLoaderClientPtr url_loader_client_ptr_to_pass;
-  binding_.Bind(mojo::MakeRequest(&url_loader_client_ptr_to_pass));
-
-  mojom::URLLoaderFactory* factory = nullptr;
-  // This |factory_ptr| will be destroyed when it goes out of scope. Because
-  // |url_loader_associated_ptr_| is associated with it, it will be disconnected
-  // as well. That means NavigationURLLoaderNetworkService::FollowRedirect()
-  // won't work as expected, the |url_loader_associated_ptr_| will silently drop
-  // calls.
-  // This is fine for now since the only user of this is WebUI which doesn't
-  // need this, but we'll have to fix this when other consumers come up.
-  mojom::URLLoaderFactoryPtr factory_ptr;
-  const auto& schemes = URLDataManagerBackend::GetWebUISchemes();
-  if (std::find(schemes.begin(), schemes.end(), request->url.scheme()) !=
-      schemes.end()) {
-    FrameTreeNode* frame_tree_node =
-        FrameTreeNode::GloballyFindByID(request_info_->frame_tree_node_id);
-    factory_ptr = GetWebUIURLLoader(frame_tree_node);
-    factory = factory_ptr.get();
-  }
-
-  if (!factory) {
-    // If a URLLoaderFactory was provided, then we use that one, otherwise
-    // fall back to connecting directly to the network service.
-    if (url_loader_factory_info.is_valid()) {
-      url_loader_factory_.Bind(std::move(url_loader_factory_info));
-      factory = url_loader_factory_.get();
-    } else {
-      factory = GetURLLoaderFactory();
-    }
-  }
-
-  factory->CreateLoaderAndStart(mojo::MakeRequest(&url_loader_associated_ptr_),
-                                0 /* routing_id? */, 0 /* request_id? */,
-                                mojom::kURLLoadOptionSendSSLInfo, *request,
-                                std::move(url_loader_client_ptr_to_pass));
-}
-
-mojom::URLLoaderFactory*
-NavigationURLLoaderNetworkService::GetURLLoaderFactory() {
-  // TODO(yzshen): We will need the ability to customize the factory per frame
-  // e.g., for appcache or service worker.
-  if (!g_url_loader_factory.Get()) {
-    ServiceManagerConnection::GetForProcess()->GetConnector()->BindInterface(
-        mojom::kNetworkServiceName, &g_url_loader_factory.Get());
-  }
-
-  return g_url_loader_factory.Get().get();
-}
-
 }  // namespace content
diff --git a/content/browser/loader/navigation_url_loader_network_service.h b/content/browser/loader/navigation_url_loader_network_service.h
index b5700e1..50ff253 100644
--- a/content/browser/loader/navigation_url_loader_network_service.h
+++ b/content/browser/loader/navigation_url_loader_network_service.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_LOADER_NAVIGATION_URL_LOADER_NETWORK_SERVICE_H_
 
 #include "base/macros.h"
-#include "base/memory/weak_ptr.h"
 #include "content/browser/loader/navigation_url_loader.h"
 #include "content/common/content_export.h"
 #include "content/common/url_loader.mojom.h"
@@ -66,27 +65,15 @@
   void OnComplete(
       const ResourceRequestCompletionStatus& completion_status) override;
 
-  // Initiates the request.
-  void StartURLRequest(mojom::URLLoaderFactoryPtrInfo url_loader_factory_info,
-                       std::unique_ptr<ResourceRequest> request);
-
  private:
-  void ConnectURLLoaderFactory(
-      std::unique_ptr<service_manager::Connector> connector);
-
-  mojom::URLLoaderFactory* GetURLLoaderFactory();
-
   NavigationURLLoaderDelegate* delegate_;
 
-  mojom::URLLoaderFactoryPtr url_loader_factory_;
   mojo::Binding<mojom::URLLoaderClient> binding_;
   std::unique_ptr<NavigationRequestInfo> request_info_;
   mojom::URLLoaderAssociatedPtr url_loader_associated_ptr_;
   scoped_refptr<ResourceResponse> response_;
   SSLStatus ssl_status_;
 
-  base::WeakPtrFactory<NavigationURLLoaderNetworkService> weak_factory_;
-
   DISALLOW_COPY_AND_ASSIGN(NavigationURLLoaderNetworkService);
 };
 
diff --git a/content/browser/memory/memory_condition_observer.cc b/content/browser/memory/memory_condition_observer.cc
index 0891302d..075beab4 100644
--- a/content/browser/memory/memory_condition_observer.cc
+++ b/content/browser/memory/memory_condition_observer.cc
@@ -13,7 +13,7 @@
 
 namespace {
 
-// A expected renderer size. These values come from the median of appropriate
+// An expected renderer size. These values come from the median of appropriate
 // UMA stats.
 #if defined(OS_ANDROID) || defined(OS_IOS)
 const int kDefaultExpectedRendererSizeMB = 40;
@@ -31,7 +31,7 @@
 const int kDefaultMonitoringIntervalSeconds = 1;
 const int kMonitoringIntervalBackgroundedSeconds = 120;
 
-void SetIntVariationParameter(const std::map<std::string, std::string> params,
+void SetIntVariationParameter(const std::map<std::string, std::string>& params,
                               const char* name,
                               int* target) {
   const auto& iter = params.find(name);
@@ -45,7 +45,7 @@
 }
 
 void SetSecondsVariationParameter(
-    const std::map<std::string, std::string> params,
+    const std::map<std::string, std::string>& params,
     const char* name,
     base::TimeDelta* target) {
   const auto& iter = params.find(name);
diff --git a/content/browser/renderer_host/input/render_widget_host_latency_tracker.cc b/content/browser/renderer_host/input/render_widget_host_latency_tracker.cc
index 2e07058..26f0c1e 100644
--- a/content/browser/renderer_host/input/render_widget_host_latency_tracker.cc
+++ b/content/browser/renderer_host/input/render_widget_host_latency_tracker.cc
@@ -31,7 +31,9 @@
   if (type == blink::WebInputEvent::kMouseWheel) {
     return "Wheel";
   } else if (WebInputEvent::IsKeyboardEventType(type)) {
-    return "Key";
+    // We should only be reporting latency for key presses.
+    DCHECK(type == WebInputEvent::kRawKeyDown || type == WebInputEvent::kChar);
+    return "KeyPress";
   } else if (WebInputEvent::IsMouseEventType(type)) {
     return "Mouse";
   } else if (WebInputEvent::IsTouchEventType(type)) {
@@ -102,8 +104,8 @@
   if (latency.coalesced())
     return;
 
-  if (type != blink::WebInputEvent::kMouseWheel &&
-      !WebInputEvent::IsTouchEventType(type)) {
+  if (latency.source_event_type() == ui::SourceEventType::UNKNOWN ||
+      latency.source_event_type() == ui::SourceEventType::OTHER) {
     return;
   }
 
@@ -124,24 +126,33 @@
     base::TimeDelta ui_delta =
         rwh_component.last_event_time - ui_component.first_event_time;
 
-    if (type == blink::WebInputEvent::kMouseWheel) {
+    if (latency.source_event_type() == ui::SourceEventType::WHEEL) {
       UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser.WheelUI",
                                   ui_delta.InMicroseconds(), 1, 20000, 100);
-    } else {
-      DCHECK(WebInputEvent::IsTouchEventType(type));
+    } else if (latency.source_event_type() == ui::SourceEventType::TOUCH) {
       UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser.TouchUI",
                                   ui_delta.InMicroseconds(), 1, 20000, 100);
+    } else if (latency.source_event_type() == ui::SourceEventType::KEY_PRESS) {
+      UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser.KeyPressUI",
+                                  ui_delta.InMicroseconds(), 1, 20000, 50);
+    } else {
+      // We should only report these histograms for wheel, touch and keyboard.
+      NOTREACHED();
     }
   }
 
-  // Both tap and scroll gestures depend on the disposition of the touch start
-  // and the current touch. For touch start, touch_start_default_prevented_ ==
-  // (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED).
+  // Touchscreen tap and scroll gestures depend on the disposition of the touch
+  // start and the current touch. For touch start,
+  // touch_start_default_prevented_ == (ack_result ==
+  // INPUT_EVENT_ACK_STATE_CONSUMED).
   bool action_prevented = touch_start_default_prevented_ ||
                           ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
 
   std::string event_name = WebInputEvent::GetName(type);
 
+  if (latency.source_event_type() == ui::KEY_PRESS)
+    event_name = "KeyPress";
+
   std::string default_action_status =
       action_prevented ? "DefaultPrevented" : "DefaultAllowed";
 
@@ -188,6 +199,11 @@
     active_multi_finger_gesture_ = touch_event.touches_length != 1;
   }
 
+  if (latency->source_event_type() == ui::KEY_PRESS) {
+    DCHECK(event.GetType() == WebInputEvent::kChar ||
+           event.GetType() == WebInputEvent::kRawKeyDown);
+  }
+
   if (latency->FindLatency(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
                            latency_component_id_, NULL)) {
     return;
diff --git a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
index 827441a..7655c4c9 100644
--- a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
+++ b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
@@ -589,6 +589,7 @@
     auto wheel = SyntheticWebMouseWheelEventBuilder::Build(
         blink::WebMouseWheelEvent::kPhaseChanged);
     ui::LatencyInfo wheel_latency;
+    wheel_latency.set_source_event_type(ui::SourceEventType::WHEEL);
     AddFakeComponents(*tracker(), &wheel_latency);
     tracker()->OnInputEvent(wheel, &wheel_latency);
     tracker()->OnInputEventAck(wheel, &wheel_latency,
@@ -602,6 +603,7 @@
     SyntheticWebTouchEvent touch;
     touch.PressPoint(0, 0);
     ui::LatencyInfo touch_latency;
+    touch_latency.set_source_event_type(ui::SourceEventType::TOUCH);
     AddFakeComponents(*tracker(), &touch_latency);
     tracker()->OnInputEvent(touch, &touch_latency);
     tracker()->OnInputEventAck(touch, &touch_latency,
@@ -629,6 +631,7 @@
     auto key_event =
         SyntheticWebKeyboardEventBuilder::Build(blink::WebKeyboardEvent::kChar);
     ui::LatencyInfo key_latency;
+    key_latency.set_source_event_type(ui::SourceEventType::KEY_PRESS);
     AddFakeComponents(*tracker(), &key_latency);
     tracker()->OnInputEvent(key_event, &key_latency);
     tracker()->OnInputEventAck(key_event, &key_latency,
@@ -725,9 +728,11 @@
       event.PressPoint(1, 1);
 
       ui::LatencyInfo latency;
+      latency.set_source_event_type(ui::SourceEventType::TOUCH);
       tracker()->OnInputEvent(event, &latency);
 
       ui::LatencyInfo fake_latency;
+      fake_latency.set_source_event_type(ui::SourceEventType::TOUCH);
       fake_latency.AddLatencyNumberWithTimestamp(
           ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
           tracker()->latency_component_id(), 0,
@@ -760,6 +765,7 @@
     {
       // Touch move.
       ui::LatencyInfo latency;
+      latency.set_source_event_type(ui::SourceEventType::TOUCH);
       event.MovePoint(0, 20, 20);
       tracker()->OnInputEvent(event, &latency);
 
@@ -772,6 +778,7 @@
       EXPECT_EQ(2U, latency.latency_components().size());
 
       ui::LatencyInfo fake_latency;
+      fake_latency.set_source_event_type(ui::SourceEventType::TOUCH);
       fake_latency.AddLatencyNumberWithTimestamp(
           ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
           tracker()->latency_component_id(), 0,
@@ -801,6 +808,7 @@
     {
       // Touch end.
       ui::LatencyInfo latency;
+      latency.set_source_event_type(ui::SourceEventType::TOUCH);
       event.ReleasePoint(0);
       tracker()->OnInputEvent(event, &latency);
 
@@ -813,6 +821,7 @@
       EXPECT_EQ(2U, latency.latency_components().size());
 
       ui::LatencyInfo fake_latency;
+      fake_latency.set_source_event_type(ui::SourceEventType::TOUCH);
       fake_latency.AddLatencyNumberWithTimestamp(
           ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
           tracker()->latency_component_id(), 0,
@@ -899,6 +908,178 @@
                   touchend_timestamps_ms[2] - touchend_timestamps_ms[1], 1)));
 }
 
+TEST_F(RenderWidgetHostLatencyTrackerTest, KeyBlockingAndQueueingTime) {
+  // These numbers are sensitive to where the histogram buckets are.
+  int event_timestamps_ms[] = {11, 25, 35};
+
+  for (InputEventAckState blocking :
+       {INPUT_EVENT_ACK_STATE_NOT_CONSUMED, INPUT_EVENT_ACK_STATE_CONSUMED}) {
+    {
+      NativeWebKeyboardEvent event(blink::WebKeyboardEvent::kRawKeyDown,
+                                   blink::WebInputEvent::kNoModifiers,
+                                   base::TimeTicks::Now());
+      ui::LatencyInfo latency_info;
+      latency_info.set_source_event_type(ui::SourceEventType::KEY_PRESS);
+      tracker()->OnInputEvent(event, &latency_info);
+
+      ui::LatencyInfo fake_latency;
+      fake_latency.set_source_event_type(ui::SourceEventType::KEY_PRESS);
+      fake_latency.AddLatencyNumberWithTimestamp(
+          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
+          tracker()->latency_component_id(), 0,
+          base::TimeTicks() +
+              base::TimeDelta::FromMilliseconds(event_timestamps_ms[0]),
+          1);
+
+      fake_latency.AddLatencyNumberWithTimestamp(
+          ui::INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT, 0, 0,
+          base::TimeTicks() +
+              base::TimeDelta::FromMilliseconds(event_timestamps_ms[1]),
+          1);
+
+      fake_latency.AddLatencyNumberWithTimestamp(
+          ui::INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT, 0, 0,
+          base::TimeTicks() +
+              base::TimeDelta::FromMilliseconds(event_timestamps_ms[2]),
+          1);
+
+      // Call ComputeInputLatencyHistograms directly to avoid OnInputEventAck
+      // overwriting components.
+      tracker()->ComputeInputLatencyHistograms(
+          event.GetType(), tracker()->latency_component_id(), fake_latency,
+          blocking);
+
+      tracker()->OnInputEventAck(event, &latency_info, blocking);
+    }
+  }
+
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples(
+          "Event.Latency.QueueingTime.KeyPressDefaultPrevented"),
+      ElementsAre(Bucket(event_timestamps_ms[1] - event_timestamps_ms[0], 1)));
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples(
+          "Event.Latency.QueueingTime.KeyPressDefaultAllowed"),
+      ElementsAre(Bucket(event_timestamps_ms[1] - event_timestamps_ms[0], 1)));
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples(
+          "Event.Latency.BlockingTime.KeyPressDefaultPrevented"),
+      ElementsAre(Bucket(event_timestamps_ms[2] - event_timestamps_ms[1], 1)));
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples(
+          "Event.Latency.BlockingTime.KeyPressDefaultAllowed"),
+      ElementsAre(Bucket(event_timestamps_ms[2] - event_timestamps_ms[1], 1)));
+}
+
+TEST_F(RenderWidgetHostLatencyTrackerTest, KeyUILatency) {
+  // These numbers are sensitive to where the histogram buckets are.
+  int event_timestamps_microseconds[] = {100, 185};
+
+  NativeWebKeyboardEvent event(blink::WebKeyboardEvent::kChar,
+                               blink::WebInputEvent::kNoModifiers,
+                               base::TimeTicks::Now());
+  ui::LatencyInfo latency_info;
+  latency_info.set_source_event_type(ui::SourceEventType::KEY_PRESS);
+  latency_info.AddLatencyNumberWithTimestamp(
+      ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0,
+      base::TimeTicks() +
+          base::TimeDelta::FromMicroseconds(event_timestamps_microseconds[0]),
+      1);
+
+  latency_info.AddLatencyNumberWithTimestamp(
+      ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
+      tracker()->latency_component_id(), 0,
+      base::TimeTicks() +
+          base::TimeDelta::FromMicroseconds(event_timestamps_microseconds[1]),
+      1);
+
+  tracker()->OnInputEvent(event, &latency_info);
+  tracker()->OnInputEventAck(event, &latency_info,
+                             InputEventAckState::INPUT_EVENT_ACK_STATE_UNKNOWN);
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples("Event.Latency.Browser.KeyPressUI"),
+      ElementsAre(Bucket(
+          event_timestamps_microseconds[1] - event_timestamps_microseconds[0],
+          1)));
+}
+
+TEST_F(RenderWidgetHostLatencyTrackerTest, KeyAckedLatency) {
+  // These numbers are sensitive to where the histogram buckets are.
+  int event_timestamps_microseconds[] = {11, 24};
+
+  NativeWebKeyboardEvent event(blink::WebKeyboardEvent::kRawKeyDown,
+                               blink::WebInputEvent::kNoModifiers,
+                               base::TimeTicks::Now());
+  ui::LatencyInfo latency_info;
+  latency_info.set_source_event_type(ui::SourceEventType::KEY_PRESS);
+
+  latency_info.AddLatencyNumberWithTimestamp(
+      ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
+      tracker()->latency_component_id(), 0,
+      base::TimeTicks() +
+          base::TimeDelta::FromMicroseconds(event_timestamps_microseconds[0]),
+      1);
+
+  latency_info.AddLatencyNumberWithTimestamp(
+      ui::INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT, 0, 0,
+      base::TimeTicks() +
+          base::TimeDelta::FromMicroseconds(event_timestamps_microseconds[1]),
+      1);
+
+  tracker()->OnInputEvent(event, &latency_info);
+  // Call ComputeInputLatencyHistograms directly to avoid OnInputEventAck
+  // overwriting components.
+  tracker()->ComputeInputLatencyHistograms(
+      event.GetType(), tracker()->latency_component_id(), latency_info,
+      InputEventAckState::INPUT_EVENT_ACK_STATE_UNKNOWN);
+
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples("Event.Latency.Browser.KeyPressAcked"),
+      ElementsAre(Bucket(
+          event_timestamps_microseconds[1] - event_timestamps_microseconds[0],
+          1)));
+}
+
+TEST_F(RenderWidgetHostLatencyTrackerTest, KeyEndToEndLatency) {
+  // These numbers are sensitive to where the histogram buckets are.
+  int event_timestamps_microseconds[] = {11, 24};
+
+  ui::LatencyInfo latency_info;
+  latency_info.set_source_event_type(ui::SourceEventType::KEY_PRESS);
+  latency_info.AddLatencyNumberWithTimestamp(
+      ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, 0,
+      base::TimeTicks() +
+          base::TimeDelta::FromMicroseconds(event_timestamps_microseconds[0]),
+      1);
+
+  latency_info.AddLatencyNumberWithTimestamp(
+      ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
+      tracker()->latency_component_id(), 0,
+      base::TimeTicks() +
+          base::TimeDelta::FromMicroseconds(event_timestamps_microseconds[0]),
+      1);
+
+  latency_info.AddLatencyNumberWithTimestamp(
+      ui::INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT, 0, 0,
+      base::TimeTicks() +
+          base::TimeDelta::FromMicroseconds(event_timestamps_microseconds[1]),
+      1);
+
+  latency_info.AddLatencyNumberWithTimestamp(
+      ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0,
+      base::TimeTicks() +
+          base::TimeDelta::FromMicroseconds(event_timestamps_microseconds[1]),
+      1);
+
+  tracker()->OnGpuSwapBuffersCompleted(latency_info);
+
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples("Event.Latency.EndToEnd.KeyPress"),
+      ElementsAre(Bucket(
+          event_timestamps_microseconds[1] - event_timestamps_microseconds[0],
+          1)));
+}
+
 // Event.Latency.(Queueing|Blocking)Time.* histograms shouldn't be reported for
 // multi-finger touch.
 TEST_F(RenderWidgetHostLatencyTrackerTest,
@@ -966,6 +1147,7 @@
   {
     // First touch start.
     ui::LatencyInfo latency;
+    latency.set_source_event_type(ui::SourceEventType::TOUCH);
     touch_event.PressPoint(1, 1);
     tracker()->OnInputEvent(touch_event, &latency);
     tracker()->OnInputEventAck(touch_event, &latency, ack_state);
@@ -974,6 +1156,7 @@
   {
     // Second touch start.
     ui::LatencyInfo latency;
+    latency.set_source_event_type(ui::SourceEventType::TOUCH);
     touch_event.PressPoint(1, 1);
     tracker()->OnInputEvent(touch_event, &latency);
     tracker()->OnInputEventAck(touch_event, &latency, ack_state);
@@ -982,6 +1165,7 @@
   {
     // Wheel event.
     ui::LatencyInfo latency;
+    latency.set_source_event_type(ui::SourceEventType::WHEEL);
     // These numbers are sensitive to where the histogram buckets are.
     int timestamps_ms[] = {11, 25, 35};
     auto wheel_event = SyntheticWebMouseWheelEventBuilder::Build(
@@ -989,6 +1173,7 @@
     tracker()->OnInputEvent(touch_event, &latency);
 
     ui::LatencyInfo fake_latency;
+    fake_latency.set_source_event_type(ui::SourceEventType::TOUCH);
     fake_latency.AddLatencyNumberWithTimestamp(
         ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
         tracker()->latency_component_id(), 0,
diff --git a/content/browser/renderer_host/input/synthetic_gesture_target_aura.cc b/content/browser/renderer_host/input/synthetic_gesture_target_aura.cc
index 9538287..08f560fa 100644
--- a/content/browser/renderer_host/input/synthetic_gesture_target_aura.cc
+++ b/content/browser/renderer_host/input/synthetic_gesture_target_aura.cc
@@ -12,6 +12,7 @@
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_view_aura.h"
 #include "content/browser/renderer_host/ui_events_helper.h"
+#include "ui/aura/event_injector.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/events/event_sink.h"
@@ -46,9 +47,10 @@
 
   aura::Window* window = GetWindow();
   aura::WindowTreeHost* host = window->GetHost();
+  aura::EventInjector injector;
+
   for (const auto& event : events) {
     event->ConvertLocationToTarget(window, host->window());
-
     // Apply the screen scale factor to the event location after it has been
     // transformed to the target.
     gfx::PointF device_location =
@@ -57,8 +59,7 @@
         gfx::ScalePoint(event->root_location_f(), device_scale_factor_);
     event->set_location_f(device_location);
     event->set_root_location_f(device_root_location);
-    ui::EventDispatchDetails details =
-        host->event_sink()->OnEventFromSource(event.get());
+    ui::EventDispatchDetails details = injector.Inject(host, event.get());
     if (details.dispatcher_destroyed)
       break;
   }
@@ -77,8 +78,9 @@
 
   aura::Window* window = GetWindow();
   wheel_event.ConvertLocationToTarget(window, window->GetRootWindow());
+  aura::EventInjector injector;
   ui::EventDispatchDetails details =
-      window->GetHost()->event_sink()->OnEventFromSource(&wheel_event);
+      injector.Inject(window->GetHost(), &wheel_event);
   if (details.dispatcher_destroyed)
     return;
 }
@@ -162,8 +164,9 @@
 
   aura::Window* window = GetWindow();
   mouse_event.ConvertLocationToTarget(window, window->GetRootWindow());
+  aura::EventInjector injector;
   ui::EventDispatchDetails details =
-      window->GetHost()->event_sink()->OnEventFromSource(&mouse_event);
+      injector.Inject(window->GetHost(), &mouse_event);
   if (details.dispatcher_destroyed)
     return;
 }
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index dbc1a4d..c036914 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -1045,8 +1045,8 @@
 }
 
 void RenderWidgetHostImpl::ForwardMouseEventWithLatencyInfo(
-      const blink::WebMouseEvent& mouse_event,
-      const ui::LatencyInfo& ui_latency) {
+    const blink::WebMouseEvent& mouse_event,
+    const ui::LatencyInfo& latency) {
   TRACE_EVENT2("input", "RenderWidgetHostImpl::ForwardMouseEvent", "x",
                mouse_event.PositionInWidget().x, "y",
                mouse_event.PositionInWidget().y);
@@ -1062,7 +1062,7 @@
   if (touch_emulator_ && touch_emulator_->HandleMouseEvent(mouse_event))
     return;
 
-  MouseEventWithLatencyInfo mouse_with_latency(mouse_event, ui_latency);
+  MouseEventWithLatencyInfo mouse_with_latency(mouse_event, latency);
   DispatchInputEventWithLatencyInfo(mouse_event, &mouse_with_latency.latency);
   input_router_->SendMouseEvent(mouse_with_latency);
 }
@@ -1074,8 +1074,8 @@
 }
 
 void RenderWidgetHostImpl::ForwardWheelEventWithLatencyInfo(
-      const blink::WebMouseWheelEvent& wheel_event,
-      const ui::LatencyInfo& ui_latency) {
+    const blink::WebMouseWheelEvent& wheel_event,
+    const ui::LatencyInfo& latency) {
   TRACE_EVENT2("input", "RenderWidgetHostImpl::ForwardWheelEvent", "dx",
                wheel_event.delta_x, "dy", wheel_event.delta_y);
 
@@ -1085,7 +1085,7 @@
   if (touch_emulator_ && touch_emulator_->HandleMouseWheelEvent(wheel_event))
     return;
 
-  MouseWheelEventWithLatencyInfo wheel_with_latency(wheel_event, ui_latency);
+  MouseWheelEventWithLatencyInfo wheel_with_latency(wheel_event, latency);
   DispatchInputEventWithLatencyInfo(wheel_event, &wheel_with_latency.latency);
   input_router_->SendWheelEvent(wheel_with_latency);
 }
@@ -1105,7 +1105,7 @@
 
 void RenderWidgetHostImpl::ForwardGestureEventWithLatencyInfo(
     const blink::WebGestureEvent& gesture_event,
-    const ui::LatencyInfo& ui_latency) {
+    const ui::LatencyInfo& latency) {
   TRACE_EVENT0("input", "RenderWidgetHostImpl::ForwardGestureEvent");
   // Early out if necessary, prior to performing latency logic.
   if (ShouldDropInputEvents())
@@ -1156,7 +1156,7 @@
   if (delegate_->PreHandleGestureEvent(gesture_event))
     return;
 
-  GestureEventWithLatencyInfo gesture_with_latency(gesture_event, ui_latency);
+  GestureEventWithLatencyInfo gesture_with_latency(gesture_event, latency);
   DispatchInputEventWithLatencyInfo(gesture_event,
                                     &gesture_with_latency.latency);
   input_router_->SendGestureEvent(gesture_with_latency);
@@ -1179,14 +1179,14 @@
 }
 
 void RenderWidgetHostImpl::ForwardTouchEventWithLatencyInfo(
-      const blink::WebTouchEvent& touch_event,
-      const ui::LatencyInfo& ui_latency) {
+    const blink::WebTouchEvent& touch_event,
+    const ui::LatencyInfo& latency) {
   TRACE_EVENT0("input", "RenderWidgetHostImpl::ForwardTouchEvent");
 
   // Always forward TouchEvents for touch stream consistency. They will be
   // ignored if appropriate in FilterInputEvent().
 
-  TouchEventWithLatencyInfo touch_with_latency(touch_event, ui_latency);
+  TouchEventWithLatencyInfo touch_with_latency(touch_event, latency);
   if (touch_emulator_ &&
       touch_emulator_->HandleTouchEvent(touch_with_latency.event)) {
     if (view_) {
@@ -1202,11 +1202,24 @@
 
 void RenderWidgetHostImpl::ForwardKeyboardEvent(
     const NativeWebKeyboardEvent& key_event) {
-  ForwardKeyboardEventWithCommands(key_event, nullptr, nullptr);
+  ui::LatencyInfo latency_info;
+
+  if (key_event.GetType() == WebInputEvent::kRawKeyDown ||
+      key_event.GetType() == WebInputEvent::kChar) {
+    latency_info.set_source_event_type(ui::SourceEventType::KEY_PRESS);
+  }
+  ForwardKeyboardEventWithLatencyInfo(key_event, latency_info);
+}
+
+void RenderWidgetHostImpl::ForwardKeyboardEventWithLatencyInfo(
+    const NativeWebKeyboardEvent& key_event,
+    const ui::LatencyInfo& latency) {
+  ForwardKeyboardEventWithCommands(key_event, latency, nullptr, nullptr);
 }
 
 void RenderWidgetHostImpl::ForwardKeyboardEventWithCommands(
     const NativeWebKeyboardEvent& key_event,
+    const ui::LatencyInfo& latency,
     const std::vector<EditCommand>* commands,
     bool* update_event) {
   TRACE_EVENT0("input", "RenderWidgetHostImpl::ForwardKeyboardEvent");
@@ -1283,9 +1296,8 @@
 
   if (touch_emulator_ && touch_emulator_->HandleKeyboardEvent(key_event))
     return;
-  ui::LatencyInfo latency_info(ui::SourceEventType::OTHER);
   NativeWebKeyboardEventWithLatencyInfo key_event_with_latency(key_event,
-                                                               latency_info);
+                                                               latency);
   key_event_with_latency.event.is_browser_shortcut = is_shortcut;
   DispatchInputEventWithLatencyInfo(key_event, &key_event_with_latency.latency);
   // TODO(foolip): |InputRouter::SendKeyboardEvent()| may filter events, in
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index a9807e79..0a5c0db 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -345,23 +345,26 @@
   // aura.
   void ForwardKeyboardEventWithCommands(
       const NativeWebKeyboardEvent& key_event,
+      const ui::LatencyInfo& latency,
       const std::vector<EditCommand>* commands,
       bool* update_event = nullptr);
 
   // Forwards the given message to the renderer. These are called by the view
   // when it has received a message.
+  void ForwardKeyboardEventWithLatencyInfo(
+      const NativeWebKeyboardEvent& key_event,
+      const ui::LatencyInfo& latency) override;
   void ForwardGestureEventWithLatencyInfo(
       const blink::WebGestureEvent& gesture_event,
-      const ui::LatencyInfo& ui_latency) override;
+      const ui::LatencyInfo& latency) override;
   virtual void ForwardTouchEventWithLatencyInfo(
       const blink::WebTouchEvent& touch_event,
-      const ui::LatencyInfo& ui_latency);  // Virtual for testing.
-  void ForwardMouseEventWithLatencyInfo(
-      const blink::WebMouseEvent& mouse_event,
-      const ui::LatencyInfo& ui_latency);
+      const ui::LatencyInfo& latency);  // Virtual for testing.
+  void ForwardMouseEventWithLatencyInfo(const blink::WebMouseEvent& mouse_event,
+                                        const ui::LatencyInfo& latency);
   virtual void ForwardWheelEventWithLatencyInfo(
       const blink::WebMouseWheelEvent& wheel_event,
-      const ui::LatencyInfo& ui_latency);  // Virtual for testing.
+      const ui::LatencyInfo& latency);  // Virtual for testing.
 
   // Enables/disables touch emulation using mouse event. See TouchEmulator.
   void SetTouchEventEmulationEnabled(
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 0e3f32db..5760996 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -639,7 +639,8 @@
                                         GetNextSimulatedEventTimeSeconds());
     EditCommands commands;
     commands.emplace_back("name", "value");
-    host_->ForwardKeyboardEventWithCommands(native_event, &commands, nullptr);
+    host_->ForwardKeyboardEventWithCommands(native_event, ui::LatencyInfo(),
+                                            &commands, nullptr);
   }
 
   void SimulateMouseEvent(WebInputEvent::Type type) {
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 57c17da..ae875fc 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -1754,7 +1754,13 @@
   if (!target_host)
     return;
 
-  target_host->ForwardKeyboardEvent(event);
+  ui::LatencyInfo latency_info;
+  if (event.GetType() == blink::WebInputEvent::kRawKeyDown ||
+      event.GetType() == blink::WebInputEvent::kChar) {
+    latency_info.set_source_event_type(ui::SourceEventType::KEY_PRESS);
+  }
+  latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+  target_host->ForwardKeyboardEventWithLatencyInfo(event, latency_info);
 }
 
 void RenderWidgetHostViewAndroid::SendMouseEvent(
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index c3defe30..cdc4c14b 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -1231,8 +1231,9 @@
   if (host_ && (event_handler_->accept_return_character() ||
                 event.GetCharacter() != ui::VKEY_RETURN)) {
     // Send a blink::WebInputEvent::Char event to |host_|.
-    ForwardKeyboardEvent(NativeWebKeyboardEvent(event, event.GetCharacter()),
-                         nullptr);
+    ForwardKeyboardEventWithLatencyInfo(
+        NativeWebKeyboardEvent(event, event.GetCharacter()), *event.latency(),
+        nullptr);
   }
 }
 
@@ -2236,8 +2237,9 @@
   }
 }
 
-void RenderWidgetHostViewAura::ForwardKeyboardEvent(
+void RenderWidgetHostViewAura::ForwardKeyboardEventWithLatencyInfo(
     const NativeWebKeyboardEvent& event,
+    const ui::LatencyInfo& latency,
     bool* update_event) {
   RenderWidgetHostImpl* target_host = host_;
 
@@ -2264,13 +2266,14 @@
                                           it->argument()));
     }
 
-    target_host->ForwardKeyboardEventWithCommands(event, &edit_commands,
-                                                  update_event);
+    target_host->ForwardKeyboardEventWithCommands(event, latency,
+                                                  &edit_commands, update_event);
     return;
   }
 #endif
 
-  target_host->ForwardKeyboardEventWithCommands(event, nullptr, update_event);
+  target_host->ForwardKeyboardEventWithCommands(event, latency, nullptr,
+                                                update_event);
 }
 
 void RenderWidgetHostViewAura::CreateSelectionController() {
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.h b/content/browser/renderer_host/render_widget_host_view_aura.h
index b4a4c994..b0975162 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.h
+++ b/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -313,8 +313,9 @@
 
   // RenderWidgetHostViewEventHandler::Delegate:
   gfx::Rect ConvertRectToScreen(const gfx::Rect& rect) const override;
-  void ForwardKeyboardEvent(const NativeWebKeyboardEvent& event,
-                            bool* update_event) override;
+  void ForwardKeyboardEventWithLatencyInfo(const NativeWebKeyboardEvent& event,
+                                           const ui::LatencyInfo& latency,
+                                           bool* update_event) override;
   RenderFrameHostImpl* GetFocusedFrame();
   bool NeedsMouseCapture() override;
   void SetTooltipsEnabled(bool enable) override;
diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h
index 6bb3159a..2c2364fc 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -264,7 +264,8 @@
       cc::SurfaceHittestDelegate* delegate,
       const gfx::Point& point,
       gfx::Point* transformed_point);
-  virtual void ProcessKeyboardEvent(const NativeWebKeyboardEvent& event) {}
+  virtual void ProcessKeyboardEvent(const NativeWebKeyboardEvent& event,
+                                    const ui::LatencyInfo& latency) {}
   virtual void ProcessMouseEvent(const blink::WebMouseEvent& event,
                                  const ui::LatencyInfo& latency) {}
   virtual void ProcessMouseWheelEvent(const blink::WebMouseWheelEvent& event,
diff --git a/content/browser/renderer_host/render_widget_host_view_event_handler.cc b/content/browser/renderer_host/render_widget_host_view_event_handler.cc
index 980a6780..bccea9a 100644
--- a/content/browser/renderer_host/render_widget_host_view_event_handler.cc
+++ b/content/browser/renderer_host/render_widget_host_view_event_handler.cc
@@ -284,7 +284,8 @@
     SetKeyboardFocus();
     // We don't have to communicate with an input method here.
     NativeWebKeyboardEvent webkit_event(*event);
-    delegate_->ForwardKeyboardEvent(webkit_event, &mark_event_as_handled);
+    delegate_->ForwardKeyboardEventWithLatencyInfo(
+        webkit_event, *event->latency(), &mark_event_as_handled);
   }
   if (mark_event_as_handled)
     event->SetHandled();
diff --git a/content/browser/renderer_host/render_widget_host_view_event_handler.h b/content/browser/renderer_host/render_widget_host_view_event_handler.h
index 8f80ee7..acf53312 100644
--- a/content/browser/renderer_host/render_widget_host_view_event_handler.h
+++ b/content/browser/renderer_host/render_widget_host_view_event_handler.h
@@ -62,8 +62,10 @@
     // to the renderer instead. |update_event| (if non-null) is set to indicate
     // whether ui::KeyEvent::SetHandled() should be called on the underlying
     // ui::KeyEvent.
-    virtual void ForwardKeyboardEvent(const NativeWebKeyboardEvent& event,
-                                      bool* update_event) = 0;
+    virtual void ForwardKeyboardEventWithLatencyInfo(
+        const NativeWebKeyboardEvent& event,
+        const ui::LatencyInfo& latency,
+        bool* update_event) = 0;
     // Returns whether the widget needs to grab mouse capture to work properly.
     virtual bool NeedsMouseCapture() = 0;
     virtual void SetTooltipsEnabled(bool enable) = 0;
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index e9ff56d..8394c80 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -2110,6 +2110,13 @@
   DCHECK(widgetHost);
 
   NativeWebKeyboardEvent event(theEvent);
+  ui::LatencyInfo latency_info;
+  if (event.GetType() == blink::WebInputEvent::kRawKeyDown ||
+      event.GetType() == blink::WebInputEvent::kChar) {
+    latency_info.set_source_event_type(ui::SourceEventType::KEY_PRESS);
+  }
+
+  latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
 
   // Force fullscreen windows to close on Escape so they won't keep the keyboard
   // grabbed or be stuck onscreen if the renderer is hanging.
@@ -2149,7 +2156,7 @@
 
   // We only handle key down events and just simply forward other events.
   if ([theEvent type] != NSKeyDown) {
-    widgetHost->ForwardKeyboardEvent(event);
+    widgetHost->ForwardKeyboardEventWithLatencyInfo(event, latency_info);
 
     // Possibly autohide the cursor.
     if ([self shouldAutohideCursorForEvent:theEvent])
@@ -2204,7 +2211,7 @@
     NativeWebKeyboardEvent fakeEvent = event;
     fakeEvent.windows_key_code = 0xE5;  // VKEY_PROCESSKEY
     fakeEvent.skip_in_browser = true;
-    widgetHost->ForwardKeyboardEvent(fakeEvent);
+    widgetHost->ForwardKeyboardEventWithLatencyInfo(fakeEvent, latency_info);
     // If this key event was handled by the input method, but
     // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
     // enqueued edit commands, then in order to let webkit handle them
@@ -2215,12 +2222,14 @@
     if (hasEditCommands_ && !hasMarkedText_)
       delayEventUntilAfterImeCompostion = YES;
   } else {
-    widgetHost->ForwardKeyboardEventWithCommands(event, &editCommands_);
+    widgetHost->ForwardKeyboardEventWithCommands(event, latency_info,
+                                                 &editCommands_);
   }
 
-  // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
-  // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
-  // be set to NULL. So we check it here and return immediately if it's NULL.
+  // Calling ForwardKeyboardEventWithCommands() could have destroyed the
+  // widget. When the widget was destroyed,
+  // |renderWidgetHostView_->render_widget_host_| will be set to NULL. So we
+  // check it here and return immediately if it's NULL.
   if (!renderWidgetHostView_->render_widget_host_)
     return;
 
@@ -2279,16 +2288,18 @@
     NativeWebKeyboardEvent fakeEvent = event;
     fakeEvent.SetType(blink::WebInputEvent::kKeyUp);
     fakeEvent.skip_in_browser = true;
-    widgetHost->ForwardKeyboardEvent(fakeEvent);
+    widgetHost->ForwardKeyboardEventWithLatencyInfo(fakeEvent, latency_info);
     // Not checking |renderWidgetHostView_->render_widget_host_| here because
     // a key event with |skip_in_browser| == true won't be handled by browser,
     // thus it won't destroy the widget.
 
-    widgetHost->ForwardKeyboardEventWithCommands(event, &editCommands_);
+    widgetHost->ForwardKeyboardEventWithCommands(event, latency_info,
+                                                 &editCommands_);
 
-    // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
-    // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
-    // be set to NULL. So we check it here and return immediately if it's NULL.
+    // Calling ForwardKeyboardEventWithCommands() could have destroyed the
+    // widget. When the widget was destroyed,
+    // |renderWidgetHostView_->render_widget_host_| will be set to NULL. So we
+    // check it here and return immediately if it's NULL.
     if (!renderWidgetHostView_->render_widget_host_)
       return;
   }
@@ -2303,7 +2314,7 @@
       event.text[0] = textToBeInserted_[0];
       event.text[1] = 0;
       event.skip_in_browser = true;
-      widgetHost->ForwardKeyboardEvent(event);
+      widgetHost->ForwardKeyboardEventWithLatencyInfo(event, latency_info);
     } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
                event.text[0] != '\0' &&
                (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
@@ -2313,7 +2324,7 @@
       // cases, unless the key event generated any other command.
       event.SetType(blink::WebInputEvent::kChar);
       event.skip_in_browser = true;
-      widgetHost->ForwardKeyboardEvent(event);
+      widgetHost->ForwardKeyboardEventWithLatencyInfo(event, latency_info);
     }
   }
 
diff --git a/content/browser/service_worker/service_worker_request_handler.cc b/content/browser/service_worker/service_worker_request_handler.cc
index e23397d4..ac4edb27 100644
--- a/content/browser/service_worker/service_worker_request_handler.cc
+++ b/content/browser/service_worker/service_worker_request_handler.cc
@@ -7,6 +7,7 @@
 #include <string>
 #include <utility>
 
+#include "base/command_line.h"
 #include "base/macros.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
@@ -20,6 +21,7 @@
 #include "content/public/browser/resource_context.h"
 #include "content/public/common/browser_side_navigation_policy.h"
 #include "content/public/common/child_process_host.h"
+#include "content/public/common/content_switches.h"
 #include "content/public/common/origin_util.h"
 #include "ipc/ipc_message.h"
 #include "net/base/url_util.h"
@@ -131,6 +133,28 @@
   navigation_handle_core->DidPreCreateProviderHost(std::move(provider_host));
 }
 
+// PlzNavigate and --enable-network-service.
+// static
+mojom::URLLoaderFactoryPtr
+ServiceWorkerRequestHandler::InitializeForNavigationNetworkService(
+    const ResourceRequest& resource_request,
+    ResourceContext* resource_context,
+    ServiceWorkerNavigationHandleCore* navigation_handle_core,
+    storage::BlobStorageContext* blob_storage_context,
+    bool skip_service_worker,
+    ResourceType resource_type,
+    RequestContextType request_context_type,
+    RequestContextFrameType frame_type,
+    bool is_parent_frame_secure,
+    scoped_refptr<ResourceRequestBodyImpl> body,
+    const base::Callback<WebContents*(void)>& web_contents_getter) {
+  DCHECK(IsBrowserSideNavigationEnabled() &&
+         base::CommandLine::ForCurrentProcess()->HasSwitch(
+             switches::kEnableNetworkService));
+  // TODO(scottmg): Currently being implemented. See https://crbug.com/715640.
+  return mojom::URLLoaderFactoryPtr();
+}
+
 void ServiceWorkerRequestHandler::InitializeHandler(
     net::URLRequest* request,
     ServiceWorkerContextWrapper* context_wrapper,
diff --git a/content/browser/service_worker/service_worker_request_handler.h b/content/browser/service_worker/service_worker_request_handler.h
index 61dd8b5..98d24569 100644
--- a/content/browser/service_worker/service_worker_request_handler.h
+++ b/content/browser/service_worker/service_worker_request_handler.h
@@ -15,6 +15,7 @@
 #include "content/common/content_export.h"
 #include "content/common/service_worker/service_worker_status_code.h"
 #include "content/common/service_worker/service_worker_types.h"
+#include "content/common/url_loader_factory.mojom.h"
 #include "content/public/common/request_context_frame_type.h"
 #include "content/public/common/request_context_type.h"
 #include "content/public/common/resource_type.h"
@@ -60,6 +61,22 @@
       scoped_refptr<ResourceRequestBodyImpl> body,
       const base::Callback<WebContents*(void)>& web_contents_getter);
 
+  // PlzNavigate and --enable-network-service.
+  // Same as InitializeForNavigation() but instead of attaching to a URLRequest,
+  // returns a URLLoaderFactoryPtrInfo if the request needs to be handled.
+  static mojom::URLLoaderFactoryPtr InitializeForNavigationNetworkService(
+      const ResourceRequest& resource_request,
+      ResourceContext* resource_context,
+      ServiceWorkerNavigationHandleCore* navigation_handle_core,
+      storage::BlobStorageContext* blob_storage_context,
+      bool skip_service_worker,
+      ResourceType resource_type,
+      RequestContextType request_context_type,
+      RequestContextFrameType frame_type,
+      bool is_parent_frame_secure,
+      scoped_refptr<ResourceRequestBodyImpl> body,
+      const base::Callback<WebContents*(void)>& web_contents_getter);
+
   // Attaches a newly created handler if the given |request| needs to
   // be handled by ServiceWorker.
   // TODO(kinuko): While utilizing UserData to attach data to URLRequest
diff --git a/content/browser/site_per_process_mac_browsertest.mm b/content/browser/site_per_process_mac_browsertest.mm
index 43e5f344..5cae49a 100644
--- a/content/browser/site_per_process_mac_browsertest.mm
+++ b/content/browser/site_per_process_mac_browsertest.mm
@@ -58,6 +58,54 @@
   DISALLOW_COPY_AND_ASSIGN(TextInputClientMacHelper);
 };
 
+// Class to detect incoming gesture event acks for scrolling tests.
+class InputEventAckWaiter
+    : public content::RenderWidgetHost::InputEventObserver {
+ public:
+  InputEventAckWaiter(blink::WebInputEvent::Type ack_type_waiting_for)
+      : message_loop_runner_(new content::MessageLoopRunner),
+        ack_type_waiting_for_(ack_type_waiting_for),
+        desired_ack_type_received_(false) {}
+  ~InputEventAckWaiter() override {}
+
+  void OnInputEventAck(const blink::WebInputEvent& event) override {
+    if (event.GetType() != ack_type_waiting_for_)
+      return;
+
+    // Ignore synthetic GestureScrollBegin/Ends.
+    if ((event.GetType() == blink::WebInputEvent::kGestureScrollBegin &&
+         static_cast<const blink::WebGestureEvent&>(event)
+             .data.scroll_begin.synthetic) ||
+        (event.GetType() == blink::WebInputEvent::kGestureScrollEnd &&
+         static_cast<const blink::WebGestureEvent&>(event)
+             .data.scroll_end.synthetic)) {
+      return;
+    }
+
+    desired_ack_type_received_ = true;
+    if (message_loop_runner_->loop_running())
+      message_loop_runner_->Quit();
+  }
+
+  void Wait() {
+    if (!desired_ack_type_received_) {
+      message_loop_runner_->Run();
+    }
+  }
+
+  void Reset() {
+    desired_ack_type_received_ = false;
+    message_loop_runner_ = new content::MessageLoopRunner;
+  }
+
+ private:
+  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+  blink::WebInputEvent::Type ack_type_waiting_for_;
+  bool desired_ack_type_received_;
+
+  DISALLOW_COPY_AND_ASSIGN(InputEventAckWaiter);
+};
+
 }  // namespace
 
 // Site per process browser tests inside content which are specific to Mac OSX
@@ -117,4 +165,91 @@
   EXPECT_EQ(word, helper.word());
   EXPECT_EQ("This", word);
 }
+
+// Ensure that the RWHVCF forwards wheel events with phase ending information.
+// RWHVCF may see wheel events with phase ending information that have deltas
+// of 0. These should not be dropped, otherwise MouseWheelEventQueue will not
+// be informed that the user's gesture has ended.
+// See crbug.com/628742
+IN_PROC_BROWSER_TEST_F(SitePerProcessMacBrowserTest,
+                       ForwardWheelEventsWithPhaseEndingInformation) {
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b)"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+                            ->GetFrameTree()
+                            ->root();
+  ASSERT_EQ(1U, root->child_count());
+
+  FrameTreeNode* child_iframe_node = root->child_at(0);
+  RenderWidgetHost* child_rwh =
+      child_iframe_node->current_frame_host()->GetRenderWidgetHost();
+
+  std::unique_ptr<InputEventAckWaiter> gesture_scroll_begin_ack_observer =
+      base::MakeUnique<InputEventAckWaiter>(
+          blink::WebInputEvent::kGestureScrollBegin);
+  std::unique_ptr<InputEventAckWaiter> gesture_scroll_end_ack_observer =
+      base::MakeUnique<InputEventAckWaiter>(
+          blink::WebInputEvent::kGestureScrollEnd);
+  child_rwh->AddInputEventObserver(gesture_scroll_begin_ack_observer.get());
+  child_rwh->AddInputEventObserver(gesture_scroll_end_ack_observer.get());
+
+  RenderWidgetHostViewBase* child_rwhv =
+      static_cast<RenderWidgetHostViewBase*>(child_rwh->GetView());
+
+  blink::WebMouseWheelEvent scroll_event(
+      blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
+      blink::WebInputEvent::kTimeStampForTesting);
+  scroll_event.SetPositionInWidget(1, 1);
+  scroll_event.has_precise_scrolling_deltas = true;
+  scroll_event.delta_x = 0.0f;
+
+  // Have the RWHVCF process a sequence of touchpad scroll events that contain
+  // phase informaiton. We start scrolling normally, then we fling.
+  // We wait for GestureScrollBegin/Ends that result from these wheel events.
+  // If we don't see them, this test will time out indicating failure.
+
+  // Begin scrolling.
+  scroll_event.delta_y = -1.0f;
+  scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
+  scroll_event.momentum_phase = blink::WebMouseWheelEvent::kPhaseNone;
+  child_rwhv->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
+  gesture_scroll_begin_ack_observer->Wait();
+
+  scroll_event.delta_y = -2.0f;
+  scroll_event.phase = blink::WebMouseWheelEvent::kPhaseChanged;
+  scroll_event.momentum_phase = blink::WebMouseWheelEvent::kPhaseNone;
+  child_rwhv->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
+
+  // End of non-momentum scrolling.
+  scroll_event.delta_y = 0.0f;
+  scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded;
+  scroll_event.momentum_phase = blink::WebMouseWheelEvent::kPhaseNone;
+  child_rwhv->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
+  gesture_scroll_end_ack_observer->Wait();
+
+  gesture_scroll_begin_ack_observer->Reset();
+  gesture_scroll_end_ack_observer->Reset();
+
+  // We now go into a fling.
+  scroll_event.delta_y = -2.0f;
+  scroll_event.phase = blink::WebMouseWheelEvent::kPhaseNone;
+  scroll_event.momentum_phase = blink::WebMouseWheelEvent::kPhaseBegan;
+  child_rwhv->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
+  gesture_scroll_begin_ack_observer->Wait();
+
+  scroll_event.delta_y = -2.0f;
+  scroll_event.phase = blink::WebMouseWheelEvent::kPhaseNone;
+  scroll_event.momentum_phase = blink::WebMouseWheelEvent::kPhaseChanged;
+  child_rwhv->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
+
+  // End of fling momentum.
+  scroll_event.delta_y = 0.0f;
+  scroll_event.phase = blink::WebMouseWheelEvent::kPhaseNone;
+  scroll_event.momentum_phase = blink::WebMouseWheelEvent::kPhaseEnded;
+  child_rwhv->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
+  gesture_scroll_end_ack_observer->Wait();
+}
+
 }  // namespace content
diff --git a/content/child/OWNERS b/content/child/OWNERS
index a0281e6..40e30002 100644
--- a/content/child/OWNERS
+++ b/content/child/OWNERS
@@ -1,5 +1,5 @@
 # For Blink API usage
-esprehn@chromium.org
+dglazkov@chromium.org
 
 # For resource loading
 yhirano@chromium.org
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 1add51d..b5e6ffe 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -112,11 +112,11 @@
     "java/src/org/chromium/content/browser/ActivityContentVideoViewEmbedder.java",
     "java/src/org/chromium/content/browser/AudioFocusDelegate.java",
     "java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java",
-    "java/src/org/chromium/content/browser/BaseChildProcessConnection.java",
     "java/src/org/chromium/content/browser/BindingManager.java",
     "java/src/org/chromium/content/browser/BindingManagerImpl.java",
     "java/src/org/chromium/content/browser/BrowserStartupController.java",
     "java/src/org/chromium/content/browser/ChildConnectionAllocator.java",
+    "java/src/org/chromium/content/browser/ChildProcessConnection.java",
     "java/src/org/chromium/content/browser/ChildProcessConstants.java",
     "java/src/org/chromium/content/browser/ChildProcessLauncher.java",
     "java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java",
@@ -133,12 +133,10 @@
     "java/src/org/chromium/content/browser/DeviceUtils.java",
     "java/src/org/chromium/content/browser/FloatingActionModeCallback.java",
     "java/src/org/chromium/content/browser/GpuProcessCallback.java",
-    "java/src/org/chromium/content/browser/ImportantChildProcessConnection.java",
     "java/src/org/chromium/content/browser/InterfaceRegistrarImpl.java",
     "java/src/org/chromium/content/browser/InterstitialPageDelegateAndroid.java",
     "java/src/org/chromium/content/browser/JavascriptInterface.java",
     "java/src/org/chromium/content/browser/LauncherThread.java",
-    "java/src/org/chromium/content/browser/ManagedChildProcessConnection.java",
     "java/src/org/chromium/content/browser/MediaResourceGetter.java",
     "java/src/org/chromium/content/browser/MediaSessionImpl.java",
     "java/src/org/chromium/content/browser/MemoryMonitorAndroid.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/BindingManager.java b/content/public/android/java/src/org/chromium/content/browser/BindingManager.java
index fdba176..ffa1bb0 100644
--- a/content/public/android/java/src/org/chromium/content/browser/BindingManager.java
+++ b/content/public/android/java/src/org/chromium/content/browser/BindingManager.java
@@ -31,7 +31,7 @@
      * Registers a freshly started child process. This can be called on any thread.
      * @param pid handle of the service process
      */
-    void addNewConnection(int pid, ManagedChildProcessConnection connection);
+    void addNewConnection(int pid, ChildProcessConnection connection);
 
     /**
      * Called when the service visibility changes or is determined for the first time. On low-memory
diff --git a/content/public/android/java/src/org/chromium/content/browser/BindingManagerImpl.java b/content/public/android/java/src/org/chromium/content/browser/BindingManagerImpl.java
index 601e96e..05944c8 100644
--- a/content/public/android/java/src/org/chromium/content/browser/BindingManagerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/BindingManagerImpl.java
@@ -177,12 +177,12 @@
     private ModerateBindingPool mModerateBindingPool;
 
     /**
-     * Wraps ManagedChildProcessConnection keeping track of additional information needed to manage
-     * the bindings of the connection. It goes away when the connection goes away.
+     * Wraps ChildProcessConnection keeping track of additional information needed to manage the
+     * bindings of the connection. It goes away when the connection goes away.
      */
     private class ManagedConnection {
         // The connection to the service.
-        private final ManagedChildProcessConnection mConnection;
+        private final ChildProcessConnection mConnection;
 
         // True iff there is a strong binding kept on the service because it is working in
         // foreground.
@@ -234,7 +234,7 @@
          * binding.
          * @param connection The ChildProcessConnection to add to the moderate binding pool.
          */
-        private void addConnectionToModerateBindingPool(ManagedChildProcessConnection connection) {
+        private void addConnectionToModerateBindingPool(ChildProcessConnection connection) {
             if (mModerateBindingPool != null && !connection.isStrongBindingBound()) {
                 mModerateBindingPool.addConnection(ManagedConnection.this);
             }
@@ -260,7 +260,7 @@
             mConnection.dropOomBindings();
         }
 
-        ManagedConnection(ManagedChildProcessConnection connection) {
+        ManagedConnection(ChildProcessConnection connection) {
             mConnection = connection;
         }
 
@@ -345,7 +345,7 @@
     }
 
     @Override
-    public void addNewConnection(int pid, ManagedChildProcessConnection connection) {
+    public void addNewConnection(int pid, ChildProcessConnection connection) {
         assert LauncherThread.runningOnLauncherThread();
         // This will reset the previous entry for the pid in the unlikely event of the OS
         // reusing renderer pids.
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java b/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java
index 18b591b..33a3855 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java
@@ -26,11 +26,26 @@
 public class ChildConnectionAllocator {
     private static final String TAG = "ChildConnAllocator";
 
-    // The factory used to create BaseChildProcessConnection instances.
-    private final BaseChildProcessConnection.Factory mConnectionFactory;
+    /** Factory interface. Used by tests to specialize created connections. */
+    @VisibleForTesting
+    protected interface ConnectionFactory {
+        ChildProcessConnection createConnection(ChildSpawnData spawnData,
+                ChildProcessConnection.DeathCallback deathCallback,
+                Bundle childProcessCommonParameters, String serviceClassName);
+    }
+
+    /** Default implementation of the ConnectionFactory that creates actual connections. */
+    private static class ConnectionFactoryImpl implements ConnectionFactory {
+        public ChildProcessConnection createConnection(ChildSpawnData spawnData,
+                ChildProcessConnection.DeathCallback deathCallback,
+                Bundle childProcessCommonParameters, String serviceClassName) {
+            return new ChildProcessConnection(spawnData.getContext(), deathCallback,
+                    serviceClassName, childProcessCommonParameters, spawnData.getCreationParams());
+        }
+    }
 
     // Connections to services. Indices of the array correspond to the service numbers.
-    private final BaseChildProcessConnection[] mChildProcessConnections;
+    private final ChildProcessConnection[] mChildProcessConnections;
 
     private final String mServiceClassName;
 
@@ -41,12 +56,13 @@
     // dequeue the pending spawn data from the same allocator as the connection.
     private final Queue<ChildSpawnData> mPendingSpawnQueue = new LinkedList<>();
 
+    private ConnectionFactory mConnectionFactory = new ConnectionFactoryImpl();
+
     /**
      * Factory method that retrieves the service name and number of service from the
      * AndroidManifest.xml.
      */
-    public static ChildConnectionAllocator create(Context context,
-            BaseChildProcessConnection.Factory connectionFactory, String packageName,
+    public static ChildConnectionAllocator create(Context context, String packageName,
             String serviceClassNameManifestKey, String numChildServicesManifestKey) {
         String serviceClassName = null;
         int numServices = -1;
@@ -76,8 +92,7 @@
             throw new RuntimeException("Illegal meta data value: the child service doesn't exist");
         }
 
-        return new ChildConnectionAllocator(
-                connectionFactory, packageName, serviceClassName, numServices);
+        return new ChildConnectionAllocator(packageName, serviceClassName, numServices);
     }
 
     // TODO(jcivelli): remove this method once crbug.com/693484 has been addressed.
@@ -106,17 +121,14 @@
      */
     @VisibleForTesting
     public static ChildConnectionAllocator createForTest(
-            BaseChildProcessConnection.Factory connectionFactory, String packageName,
-            String serviceClassName, int serviceCount) {
-        return new ChildConnectionAllocator(
-                connectionFactory, packageName, serviceClassName, serviceCount);
+            String packageName, String serviceClassName, int serviceCount) {
+        return new ChildConnectionAllocator(packageName, serviceClassName, serviceCount);
     }
 
-    private ChildConnectionAllocator(BaseChildProcessConnection.Factory connectionFactory,
+    private ChildConnectionAllocator(
             String packageName, String serviceClassName, int numChildServices) {
-        mConnectionFactory = connectionFactory;
         mServiceClassName = serviceClassName;
-        mChildProcessConnections = new BaseChildProcessConnection[numChildServices];
+        mChildProcessConnections = new ChildProcessConnection[numChildServices];
         mFreeConnectionIndices = new ArrayList<Integer>(numChildServices);
         for (int i = 0; i < numChildServices; i++) {
             mFreeConnectionIndices.add(i);
@@ -124,9 +136,9 @@
     }
 
     // Allocates or enqueues. If there are no free slots, returns null and enqueues the spawn data.
-    public BaseChildProcessConnection allocate(ChildSpawnData spawnData,
-            BaseChildProcessConnection.DeathCallback deathCallback,
-            Bundle childProcessCommonParameters, boolean queueIfNoSlotAvailable) {
+    public ChildProcessConnection allocate(ChildSpawnData spawnData,
+            ChildProcessConnection.DeathCallback deathCallback, Bundle childProcessCommonParameters,
+            boolean queueIfNoSlotAvailable) {
         assert LauncherThread.runningOnLauncherThread();
         if (mFreeConnectionIndices.isEmpty()) {
             Log.d(TAG, "Ran out of services to allocate.");
@@ -138,15 +150,14 @@
         int slot = mFreeConnectionIndices.remove(0);
         assert mChildProcessConnections[slot] == null;
         String serviceClassName = mServiceClassName + slot;
-        mChildProcessConnections[slot] =
-                mConnectionFactory.create(spawnData.getContext(), deathCallback, serviceClassName,
-                        childProcessCommonParameters, spawnData.getCreationParams());
+        mChildProcessConnections[slot] = mConnectionFactory.createConnection(
+                spawnData, deathCallback, childProcessCommonParameters, serviceClassName);
         Log.d(TAG, "Allocator allocated a connection, name: %s, slot: %d", mServiceClassName, slot);
         return mChildProcessConnections[slot];
     }
 
     // Also return the first ChildSpawnData in the pending queue, if any.
-    public ChildSpawnData free(BaseChildProcessConnection connection) {
+    public ChildSpawnData free(ChildProcessConnection connection) {
         assert LauncherThread.runningOnLauncherThread();
         // mChildProcessConnections is relatively short (20 items at max at this point).
         // We are better of iterating than caching in a map.
@@ -172,6 +183,11 @@
         return mChildProcessConnections.length;
     }
 
+    @VisibleForTesting
+    void setConnectionFactoryForTesting(ConnectionFactory connectionFactory) {
+        mConnectionFactory = connectionFactory;
+    }
+
     /** @return the count of connections managed by the allocator */
     @VisibleForTesting
     int allocatedConnectionsCountForTesting() {
@@ -180,7 +196,7 @@
     }
 
     @VisibleForTesting
-    BaseChildProcessConnection[] connectionArrayForTesting() {
+    ChildProcessConnection[] connectionArrayForTesting() {
         return mChildProcessConnections;
     }
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/BaseChildProcessConnection.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnection.java
similarity index 68%
rename from content/public/android/java/src/org/chromium/content/browser/BaseChildProcessConnection.java
rename to content/public/android/java/src/org/chromium/content/browser/ChildProcessConnection.java
index 77796e8..509663b 100644
--- a/content/public/android/java/src/org/chromium/content/browser/BaseChildProcessConnection.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnection.java
@@ -32,8 +32,8 @@
 /**
  * Manages a connection between the browser activity and a child service.
  */
-public abstract class BaseChildProcessConnection {
-    private static final String TAG = "BaseChildProcessConn";
+public class ChildProcessConnection {
+    private static final String TAG = "ChildProcessConn";
 
     /**
      * Used to notify the consumer about disconnection of the service. This callback is provided
@@ -42,7 +42,7 @@
      */
     interface DeathCallback {
         // Called on Launcher thread.
-        void onChildProcessDied(BaseChildProcessConnection connection);
+        void onChildProcessDied(ChildProcessConnection connection);
     }
 
     /**
@@ -71,17 +71,11 @@
          * Called when the connection to the service is established.
          * @param connecion the connection object to the child process
          */
-        void onConnected(BaseChildProcessConnection connection);
-    }
-
-    /** Used to create specialization connection instances. */
-    interface Factory {
-        BaseChildProcessConnection create(Context context, DeathCallback deathCallback,
-                String serviceClassName, Bundle childProcessCommonParameters,
-                ChildProcessCreationParams creationParams);
+        void onConnected(ChildProcessConnection connection);
     }
 
     /** Interface representing a connection to the Android service. Can be mocked in unit-tests. */
+    @VisibleForTesting
     protected interface ChildServiceConnection {
         boolean bind();
         void unbind();
@@ -89,8 +83,7 @@
     }
 
     /** Implementation of ChildServiceConnection that does connect to a service. */
-    protected class ChildServiceConnectionImpl
-            implements ChildServiceConnection, ServiceConnection {
+    private class ChildServiceConnectionImpl implements ChildServiceConnection, ServiceConnection {
         private final int mBindFlags;
         private boolean mBound;
 
@@ -111,14 +104,14 @@
         public boolean bind() {
             if (!mBound) {
                 try {
-                    TraceEvent.begin("BaseChildProcessConnection.ChildServiceConnection.bind");
+                    TraceEvent.begin("ChildProcessConnection.ChildServiceConnectionImpl.bind");
                     Intent intent = createServiceBindIntent();
                     if (mChildProcessCommonParameters != null) {
                         intent.putExtras(mChildProcessCommonParameters);
                     }
                     mBound = mContext.bindService(intent, this, mBindFlags);
                 } finally {
-                    TraceEvent.end("BaseChildProcessConnection.ChildServiceConnection.bind");
+                    TraceEvent.end("ChildProcessConnection.ChildServiceConnectionImpl.bind");
                 }
             }
             return mBound;
@@ -142,7 +135,7 @@
             LauncherThread.post(new Runnable() {
                 @Override
                 public void run() {
-                    BaseChildProcessConnection.this.onServiceConnectedOnLauncherThread(service);
+                    ChildProcessConnection.this.onServiceConnectedOnLauncherThread(service);
                 }
             });
         }
@@ -153,18 +146,16 @@
             LauncherThread.post(new Runnable() {
                 @Override
                 public void run() {
-                    BaseChildProcessConnection.this.onServiceDisconnectedOnLauncherThread();
+                    ChildProcessConnection.this.onServiceDisconnectedOnLauncherThread();
                 }
             });
         }
     }
 
-    // binding flag provided via ChildProcessCreationParams.
     // TODO(mnaganov): Get rid of it after the release of the next Android SDK.
     private static final Map<ComponentName, Boolean> sNeedsExtrabindFlagsMap = new HashMap<>();
-
     private final Context mContext;
-    private final BaseChildProcessConnection.DeathCallback mDeathCallback;
+    private final ChildProcessConnection.DeathCallback mDeathCallback;
     private final ComponentName mServiceName;
 
     // Parameters passed to the child process through the service binding intent.
@@ -216,7 +207,31 @@
     // Process ID of the corresponding child process.
     private int mPid;
 
-    protected BaseChildProcessConnection(Context context, DeathCallback deathCallback,
+    // Inital moderate binding.
+    private final ChildServiceConnection mInitialBinding;
+
+    // Strong binding will make the service priority equal to the priority of the activity.
+    private final ChildServiceConnection mStrongBinding;
+
+    // Moderate binding will make the service priority equal to the priority of a visible process
+    // while the app is in the foreground.
+    private final ChildServiceConnection mModerateBinding;
+
+    // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls
+    // to start() and stop().
+    private final ChildServiceConnection mWaivedBinding;
+
+    // Incremented on addStrongBinding(), decremented on removeStrongBinding().
+    private int mStrongBindingCount;
+
+    // Indicates whether the connection only has the waived binding (if the connection is unbound,
+    // it contains the state at time of unbinding).
+    private boolean mWaivedBoundOnly;
+
+    // Set to true once unbind() was called.
+    private boolean mUnbound;
+
+    protected ChildProcessConnection(Context context, DeathCallback deathCallback,
             String serviceClassName, Bundle childProcessCommonParameters,
             ChildProcessCreationParams creationParams) {
         assert LauncherThread.runningOnLauncherThread();
@@ -227,6 +242,13 @@
         mServiceName = new ComponentName(packageName, serviceClassName);
         mChildProcessCommonParameters = childProcessCommonParameters;
         mCreationParams = creationParams;
+
+        int defaultFlags = Context.BIND_AUTO_CREATE
+                | (shouldBindAsExportedService() ? Context.BIND_EXTERNAL_SERVICE : 0);
+        mInitialBinding = createServiceConnection(defaultFlags);
+        mModerateBinding = createServiceConnection(defaultFlags);
+        mStrongBinding = createServiceConnection(defaultFlags | Context.BIND_IMPORTANT);
+        mWaivedBinding = createServiceConnection(defaultFlags | Context.BIND_WAIVE_PRIORITY);
     }
 
     public final Context getContext() {
@@ -240,11 +262,6 @@
                                        : mContext.getPackageName();
     }
 
-    public final ChildProcessCreationParams getCreationParams() {
-        assert LauncherThread.runningOnLauncherThread();
-        return mCreationParams;
-    }
-
     public final IChildProcessService getService() {
         assert LauncherThread.runningOnLauncherThread();
         return mService;
@@ -255,6 +272,10 @@
         return mServiceName;
     }
 
+    public boolean isConnected() {
+        return mService != null;
+    }
+
     /**
      * @return the connection pid, or 0 if not yet connected
      */
@@ -267,28 +288,29 @@
      * Starts a connection to an IChildProcessService. This must be followed by a call to
      * setupConnection() to setup the connection parameters. start() and setupConnection() are
      * separate to allow to pass whatever parameters are available in start(), and complete the
-     * remainder later while reducing the connection setup latency.
+     * remainder addStrongBinding while reducing the connection setup latency.
+     * @param useStrongBinding whether a strong binding should be bound by default. If false, an
+     * initial moderate binding is used.
      * @param startCallback (optional) callback when the child process starts or fails to start.
      */
-    public void start(StartCallback startCallback) {
+    public void start(boolean useStrongBinding, StartCallback startCallback) {
         assert LauncherThread.runningOnLauncherThread();
         try {
-            TraceEvent.begin("BaseChildProcessConnection.start");
+            TraceEvent.begin("ChildProcessConnection.start");
             assert LauncherThread.runningOnLauncherThread();
             assert mConnectionParams
-                    == null
-                    : "setupConnection() called before start() in BaseChildProcessConnection.";
+                    == null : "setupConnection() called before start() in ChildProcessConnection.";
 
             mStartCallback = startCallback;
 
-            if (!bind()) {
+            if (!bind(useStrongBinding)) {
                 Log.e(TAG, "Failed to establish the service connection.");
                 // We have to notify the caller so that they can free-up associated resources.
                 // TODO(ppi): Can we hard-fail here?
-                mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this);
+                mDeathCallback.onChildProcessDied(ChildProcessConnection.this);
             }
         } finally {
-            TraceEvent.end("BaseChildProcessConnection.start");
+            TraceEvent.end("ChildProcessConnection.start");
         }
     }
 
@@ -311,7 +333,7 @@
             return;
         }
         try {
-            TraceEvent.begin("BaseChildProcessConnection.setupConnection");
+            TraceEvent.begin("ChildProcessConnection.setupConnection");
             mConnectionCallback = connectionCallback;
             mConnectionParams = new ConnectionParams(commandLine, filesToBeMapped, callback);
             // Run the setup if the service is already connected. If not,
@@ -320,7 +342,7 @@
                 doConnectionSetupLocked();
             }
         } finally {
-            TraceEvent.end("BaseChildProcessConnection.setupConnection");
+            TraceEvent.end("ChildProcessConnection.setupConnection");
         }
     }
 
@@ -343,18 +365,17 @@
             return;
         }
         try {
-            TraceEvent.begin(
-                    "BaseChildProcessConnection.ChildServiceConnection.onServiceConnected");
+            TraceEvent.begin("ChildProcessConnection.ChildServiceConnection.onServiceConnected");
             mDidOnServiceConnected = true;
             mService = IChildProcessService.Stub.asInterface(service);
 
             StartCallback startCallback = mStartCallback;
             mStartCallback = null;
 
-            final boolean bindCheck =
-                    mCreationParams != null && mCreationParams.getBindToCallerCheck();
             boolean boundToUs = false;
             try {
+                boolean bindCheck =
+                        mCreationParams != null && mCreationParams.getBindToCallerCheck();
                 boundToUs = bindCheck ? mService.bindToCaller() : true;
             } catch (RemoteException ex) {
                 // Do not trigger the StartCallback here, since the service is already
@@ -383,7 +404,7 @@
                 doConnectionSetupLocked();
             }
         } finally {
-            TraceEvent.end("BaseChildProcessConnection.ChildServiceConnection.onServiceConnected");
+            TraceEvent.end("ChildProcessConnection.ChildServiceConnection.onServiceConnected");
         }
     }
 
@@ -397,7 +418,7 @@
         mServiceDisconnected = true;
         Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=%d", mPid);
         stop(); // We don't want to auto-restart on crash. Let the browser do that.
-        mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this);
+        mDeathCallback.onChildProcessDied(ChildProcessConnection.this);
         // If we have a pending connection callback, we need to communicate the failure to
         // the caller.
         if (mConnectionCallback != null) {
@@ -423,7 +444,7 @@
      */
     private void doConnectionSetupLocked() {
         try {
-            TraceEvent.begin("BaseChildProcessConnection.doConnectionSetupLocked");
+            TraceEvent.begin("ChildProcessConnection.doConnectionSetupLocked");
             assert mServiceConnectComplete && mService != null;
             assert mConnectionParams != null;
 
@@ -455,23 +476,142 @@
             }
             mConnectionParams = null;
         } finally {
-            TraceEvent.end("BaseChildProcessConnection.doConnectionSetupLocked");
+            TraceEvent.end("ChildProcessConnection.doConnectionSetupLocked");
         }
     }
 
-    /** Subclasses should implement this method to bind/unbind to the actual service. */
-    protected abstract boolean bind();
-    protected abstract void unbind();
-
-    protected ChildServiceConnection createServiceConnection(int bindFlags) {
+    private boolean bind(boolean useStrongBinding) {
         assert LauncherThread.runningOnLauncherThread();
-        return new ChildServiceConnectionImpl(bindFlags);
+        assert !mUnbound;
+
+        boolean success = useStrongBinding ? mStrongBinding.bind() : mInitialBinding.bind();
+        if (!success) return false;
+
+        updateWaivedBoundOnlyState();
+        mWaivedBinding.bind();
+        return true;
+    }
+
+    @VisibleForTesting
+    protected void unbind() {
+        assert LauncherThread.runningOnLauncherThread();
+        mUnbound = true;
+        mStrongBinding.unbind();
+        mWaivedBinding.unbind();
+        mModerateBinding.unbind();
+        mInitialBinding.unbind();
+        // Note that we don't update the waived bound only state here as to preserve the state when
+        // disconnected.
+    }
+
+    public boolean isInitialBindingBound() {
+        assert LauncherThread.runningOnLauncherThread();
+        return mInitialBinding.isBound();
+    }
+
+    public void addInitialBinding() {
+        assert LauncherThread.runningOnLauncherThread();
+        mInitialBinding.bind();
+        updateWaivedBoundOnlyState();
+    }
+
+    public boolean isStrongBindingBound() {
+        assert LauncherThread.runningOnLauncherThread();
+        return mStrongBinding.isBound();
+    }
+
+    public void removeInitialBinding() {
+        assert LauncherThread.runningOnLauncherThread();
+        mInitialBinding.unbind();
+        updateWaivedBoundOnlyState();
+    }
+
+    public void dropOomBindings() {
+        assert LauncherThread.runningOnLauncherThread();
+        mInitialBinding.unbind();
+
+        mStrongBindingCount = 0;
+        mStrongBinding.unbind();
+        updateWaivedBoundOnlyState();
+
+        mModerateBinding.unbind();
+    }
+
+    public void addStrongBinding() {
+        assert LauncherThread.runningOnLauncherThread();
+        if (!isConnected()) {
+            Log.w(TAG, "The connection is not bound for %d", getPid());
+            return;
+        }
+        if (mStrongBindingCount == 0) {
+            mStrongBinding.bind();
+            updateWaivedBoundOnlyState();
+        }
+        mStrongBindingCount++;
+    }
+
+    public void removeStrongBinding() {
+        assert LauncherThread.runningOnLauncherThread();
+        if (!isConnected()) {
+            Log.w(TAG, "The connection is not bound for %d", getPid());
+            return;
+        }
+        assert mStrongBindingCount > 0;
+        mStrongBindingCount--;
+        if (mStrongBindingCount == 0) {
+            mStrongBinding.unbind();
+            updateWaivedBoundOnlyState();
+        }
+    }
+
+    public boolean isModerateBindingBound() {
+        assert LauncherThread.runningOnLauncherThread();
+        return mModerateBinding.isBound();
+    }
+
+    public void addModerateBinding() {
+        assert LauncherThread.runningOnLauncherThread();
+        if (!isConnected()) {
+            Log.w(TAG, "The connection is not bound for %d", getPid());
+            return;
+        }
+        mModerateBinding.bind();
+        updateWaivedBoundOnlyState();
+    }
+
+    public void removeModerateBinding() {
+        assert LauncherThread.runningOnLauncherThread();
+        if (!isConnected()) {
+            Log.w(TAG, "The connection is not bound for %d", getPid());
+            return;
+        }
+        mModerateBinding.unbind();
+        updateWaivedBoundOnlyState();
+    }
+
+    /**
+     * @return true if the connection is bound and only bound with the waived binding or if the
+     * connection is unbound and was only bound with the waived binding when it disconnected.
+     */
+    public boolean isWaivedBoundOnlyOrWasWhenDied() {
+        // WARNING: this method can be called from a thread other than the launcher thread.
+        // Note that it returns the current waived bound only state and is racy. This not really
+        // preventable without changing the caller's API, short of blocking.
+        return mWaivedBoundOnly;
+    }
+
+    // Should be called every time the mInitialBinding or mStrongBinding are bound/unbound.
+    private void updateWaivedBoundOnlyState() {
+        if (!mUnbound) {
+            mWaivedBoundOnly = !mInitialBinding.isBound() && !mStrongBinding.isBound()
+                    && !mModerateBinding.isBound();
+        }
     }
 
     protected boolean shouldBindAsExportedService() {
         assert LauncherThread.runningOnLauncherThread();
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getCreationParams() != null
-                && getCreationParams().getIsExternalService()
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && mCreationParams != null
+                && mCreationParams.getIsExternalService()
                 && isExportedService(getContext(), getServiceName());
     }
 
@@ -493,12 +633,13 @@
     }
 
     @VisibleForTesting
-    public void crashServiceForTesting() throws RemoteException {
-        mService.crashIntentionallyForTesting();
+    protected ChildServiceConnection createServiceConnection(int bindFlags) {
+        assert LauncherThread.runningOnLauncherThread();
+        return new ChildServiceConnectionImpl(bindFlags);
     }
 
     @VisibleForTesting
-    boolean isConnected() {
-        return mService != null;
+    public void crashServiceForTesting() throws RemoteException {
+        mService.crashIntentionallyForTesting();
     }
 }
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java
index d668456..c88a5d63 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java
@@ -51,7 +51,7 @@
      * Implemented by ChildProcessLauncherHelper.
      */
     public interface LaunchCallback {
-        void onChildProcessStarted(BaseChildProcessConnection connection);
+        void onChildProcessStarted(ChildProcessConnection connection);
     }
 
     private static final boolean SPARE_CONNECTION_ALWAYS_IN_FOREGROUND = false;
@@ -61,14 +61,14 @@
             sSandboxedChildConnectionAllocatorMap = new HashMap<>();
 
     // Map from a connection to its ChildConnectionAllocator.
-    private static final Map<BaseChildProcessConnection, ChildConnectionAllocator>
+    private static final Map<ChildProcessConnection, ChildConnectionAllocator>
             sConnectionsToAllocatorMap = new HashMap<>();
 
     // Allocator used for non-sandboxed services.
     private static ChildConnectionAllocator sPrivilegedChildConnectionAllocator;
 
-    // Used by test to override the default sandboxed service allocator settings.
-    private static BaseChildProcessConnection.Factory sSandboxedServiceFactoryForTesting;
+    // Used by tests to override the default sandboxed service allocator settings.
+    private static ChildConnectionAllocator.ConnectionFactory sSandboxedServiceFactoryForTesting;
     private static int sSandboxedServicesCountForTesting = -1;
     private static String sSandboxedServicesNameForTesting;
 
@@ -79,8 +79,7 @@
         if (!sandboxed) {
             if (sPrivilegedChildConnectionAllocator == null) {
                 sPrivilegedChildConnectionAllocator = ChildConnectionAllocator.create(context,
-                        ImportantChildProcessConnection.FACTORY, packageName,
-                        PRIVILEGED_SERVICES_NAME_KEY, NUM_PRIVILEGED_SERVICES_KEY);
+                        packageName, PRIVILEGED_SERVICES_NAME_KEY, NUM_PRIVILEGED_SERVICES_KEY);
             }
             return sPrivilegedChildConnectionAllocator;
         }
@@ -93,20 +92,19 @@
             ChildConnectionAllocator connectionAllocator = null;
             if (sSandboxedServicesCountForTesting != -1) {
                 // Testing case where allocator settings are overriden.
-                BaseChildProcessConnection.Factory factory =
-                        sSandboxedServiceFactoryForTesting == null
-                        ? ManagedChildProcessConnection.FACTORY
-                        : sSandboxedServiceFactoryForTesting;
                 String serviceName = !TextUtils.isEmpty(sSandboxedServicesNameForTesting)
                         ? sSandboxedServicesNameForTesting
                         : SandboxedProcessService.class.getName();
                 connectionAllocator = ChildConnectionAllocator.createForTest(
-                        factory, packageName, serviceName, sSandboxedServicesCountForTesting);
+                        packageName, serviceName, sSandboxedServicesCountForTesting);
             } else {
-                connectionAllocator = ChildConnectionAllocator.create(context,
-                        ManagedChildProcessConnection.FACTORY, packageName,
+                connectionAllocator = ChildConnectionAllocator.create(context, packageName,
                         SANDBOXED_SERVICES_NAME_KEY, NUM_SANDBOXED_SERVICES_KEY);
             }
+            if (sSandboxedServiceFactoryForTesting != null) {
+                connectionAllocator.setConnectionFactoryForTesting(
+                        sSandboxedServiceFactoryForTesting);
+            }
             sSandboxedChildConnectionAllocatorMap.put(packageName, connectionAllocator);
         }
         return sSandboxedChildConnectionAllocatorMap.get(packageName);
@@ -115,13 +113,13 @@
     }
 
     @VisibleForTesting
-    static BaseChildProcessConnection allocateConnection(
+    static ChildProcessConnection allocateConnection(
             ChildSpawnData spawnData, Bundle childProcessCommonParams, boolean forWarmUp) {
         assert LauncherThread.runningOnLauncherThread();
-        BaseChildProcessConnection.DeathCallback deathCallback =
-                new BaseChildProcessConnection.DeathCallback() {
+        ChildProcessConnection.DeathCallback deathCallback =
+                new ChildProcessConnection.DeathCallback() {
                     @Override
-                    public void onChildProcessDied(BaseChildProcessConnection connection) {
+                    public void onChildProcessDied(ChildProcessConnection connection) {
                         assert LauncherThread.runningOnLauncherThread();
                         if (connection.getPid() != 0) {
                             stop(connection.getPid());
@@ -137,7 +135,7 @@
                 creationParams != null ? creationParams.getPackageName() : context.getPackageName();
         ChildConnectionAllocator allocator =
                 getConnectionAllocator(context, packageName, inSandbox);
-        BaseChildProcessConnection connection =
+        ChildProcessConnection connection =
                 allocator.allocate(spawnData, deathCallback, childProcessCommonParams, !forWarmUp);
         sConnectionsToAllocatorMap.put(connection, allocator);
         return connection;
@@ -184,17 +182,19 @@
     }
 
     @VisibleForTesting
-    static BaseChildProcessConnection allocateBoundConnection(ChildSpawnData spawnData,
-            BaseChildProcessConnection.StartCallback startCallback, boolean forWarmUp) {
+    static ChildProcessConnection allocateBoundConnection(ChildSpawnData spawnData,
+            ChildProcessConnection.StartCallback startCallback, boolean forWarmUp) {
         assert LauncherThread.runningOnLauncherThread();
         final Context context = spawnData.getContext();
         final boolean inSandbox = spawnData.isInSandbox();
         final ChildProcessCreationParams creationParams = spawnData.getCreationParams();
 
-        BaseChildProcessConnection connection = allocateConnection(
+        ChildProcessConnection connection = allocateConnection(
                 spawnData, createCommonParamsBundle(spawnData.getCreationParams()), forWarmUp);
         if (connection != null) {
-            connection.start(startCallback);
+            // Non sandboxed processes are privileged processes that should be strongly bound.
+            boolean useStrongBinding = !inSandbox;
+            connection.start(useStrongBinding, startCallback);
 
             String packageName = creationParams != null ? creationParams.getPackageName()
                                                         : context.getPackageName();
@@ -212,7 +212,7 @@
 
     private static final long FREE_CONNECTION_DELAY_MILLIS = 1;
 
-    private static void freeConnection(BaseChildProcessConnection connection) {
+    private static void freeConnection(ChildProcessConnection connection) {
         assert LauncherThread.runningOnLauncherThread();
         if (connection == sSpareSandboxedConnection) clearSpareConnection();
 
@@ -221,7 +221,7 @@
         // alive when it's been unbound for a short time. If a new connection to the same service
         // is bound at that point, the process is reused and bad things happen (mostly static
         // variables are set when we don't expect them to).
-        final BaseChildProcessConnection conn = connection;
+        final ChildProcessConnection conn = connection;
         LauncherThread.postDelayed(new Runnable() {
             @Override
             public void run() {
@@ -246,8 +246,7 @@
     }
 
     // Map from pid to ChildService connection.
-    private static Map<Integer, BaseChildProcessConnection> sServiceMap =
-            new ConcurrentHashMap<Integer, BaseChildProcessConnection>();
+    private static Map<Integer, ChildProcessConnection> sServiceMap = new ConcurrentHashMap<>();
 
     // These variables are used for the warm up sandboxed connection.
     // |sSpareSandboxedConnection| is non-null when there is a pending connection. Note it's cleared
@@ -257,9 +256,9 @@
     // |sSpareConnectionStartCallback| is the chained StartCallback. This is also used to determine
     // if there is already a child process launch that's used this this connection.
     @SuppressLint("StaticFieldLeak")
-    private static BaseChildProcessConnection sSpareSandboxedConnection;
+    private static ChildProcessConnection sSpareSandboxedConnection;
     private static boolean sSpareConnectionStarting;
-    private static BaseChildProcessConnection.StartCallback sSpareConnectionStartCallback;
+    private static ChildProcessConnection.StartCallback sSpareConnectionStartCallback;
 
     // Manages oom bindings used to bind chind services. Lazily initialized by getBindingManager()
     private static BindingManager sBindingManager;
@@ -354,8 +353,8 @@
                 if (sSpareSandboxedConnection != null) return;
                 ChildProcessCreationParams params = ChildProcessCreationParams.getDefault();
 
-                BaseChildProcessConnection.StartCallback startCallback =
-                        new BaseChildProcessConnection.StartCallback() {
+                ChildProcessConnection.StartCallback startCallback =
+                        new ChildProcessConnection.StartCallback() {
                             @Override
                             public void onChildStarted() {
                                 assert LauncherThread.runningOnLauncherThread();
@@ -422,7 +421,7 @@
         if (!ContentSwitches.SWITCH_RENDERER_PROCESS.equals(processType)) {
             if (params != null && !params.getPackageName().equals(context.getPackageName())) {
                 // WebViews and WebAPKs have renderer processes running in their applications.
-                // When launching these renderer processes, {@link ManagedChildProcessConnection}
+                // When launching these renderer processes, {@link ChildProcessConnection}
                 // requires the package name of the application which holds the renderer process.
                 // Therefore, the package name in ChildProcessCreationParams could be the package
                 // name of WebViews, WebAPKs, or Chrome, depending on the host application.
@@ -450,7 +449,7 @@
     }
 
     @VisibleForTesting
-    public static BaseChildProcessConnection startInternal(final Context context,
+    public static ChildProcessConnection startInternal(final Context context,
             final String[] commandLine, final FileDescriptorInfo[] filesToBeMapped,
             final LaunchCallback launchCallback, final IBinder childProcessCallback,
             final boolean inSandbox, final boolean alwaysInForeground,
@@ -459,18 +458,18 @@
         try {
             TraceEvent.begin("ChildProcessLauncher.startInternal");
 
-            BaseChildProcessConnection allocatedConnection = null;
+            ChildProcessConnection allocatedConnection = null;
             String packageName = creationParams != null ? creationParams.getPackageName()
                     : context.getPackageName();
-            BaseChildProcessConnection.StartCallback startCallback =
-                    new BaseChildProcessConnection.StartCallback() {
+            ChildProcessConnection.StartCallback startCallback =
+                    new ChildProcessConnection.StartCallback() {
                         @Override
                         public void onChildStarted() {}
 
                         @Override
                         public void onChildStartFailed() {
                             assert LauncherThread.runningOnLauncherThread();
-                            Log.e(TAG, "BaseChildProcessConnection.start failed, trying again");
+                            Log.e(TAG, "ChildProcessConnection.start failed, trying again");
                             LauncherThread.post(new Runnable() {
                                 @Override
                                 public void run() {
@@ -512,8 +511,9 @@
                 }
             }
 
+            boolean addToBindingmanager = inSandbox;
             triggerConnectionSetup(allocatedConnection, commandLine, filesToBeMapped,
-                    childProcessCallback, launchCallback);
+                    childProcessCallback, launchCallback, addToBindingmanager);
             return allocatedConnection;
         } finally {
             TraceEvent.end("ChildProcessLauncher.startInternal");
@@ -538,23 +538,23 @@
     }
 
     @VisibleForTesting
-    static void triggerConnectionSetup(final BaseChildProcessConnection connection,
+    static void triggerConnectionSetup(final ChildProcessConnection connection,
             String[] commandLine, FileDescriptorInfo[] filesToBeMapped,
-            final IBinder childProcessCallback, final LaunchCallback launchCallback) {
+            final IBinder childProcessCallback, final LaunchCallback launchCallback,
+            final boolean addToBindingmanager) {
         assert LauncherThread.runningOnLauncherThread();
         Log.d(TAG, "Setting up connection to process, connection name=%s",
                 connection.getServiceName());
-        BaseChildProcessConnection.ConnectionCallback connectionCallback =
-                new BaseChildProcessConnection.ConnectionCallback() {
+        ChildProcessConnection.ConnectionCallback connectionCallback =
+                new ChildProcessConnection.ConnectionCallback() {
                     @Override
-                    public void onConnected(BaseChildProcessConnection connection) {
+                    public void onConnected(ChildProcessConnection connection) {
                         assert LauncherThread.runningOnLauncherThread();
                         if (connection != null) {
                             int pid = connection.getPid();
                             Log.d(TAG, "on connect callback, pid=%d", pid);
-                            if (connection instanceof ManagedChildProcessConnection) {
-                                getBindingManager().addNewConnection(
-                                        pid, (ManagedChildProcessConnection) connection);
+                            if (addToBindingmanager) {
+                                getBindingManager().addNewConnection(pid, connection);
                             }
                             sServiceMap.put(pid, connection);
                         }
@@ -579,7 +579,7 @@
     static void stop(int pid) {
         assert LauncherThread.runningOnLauncherThread();
         Log.d(TAG, "stopping child connection: pid=%d", pid);
-        BaseChildProcessConnection connection = sServiceMap.remove(pid);
+        ChildProcessConnection connection = sServiceMap.remove(pid);
         if (connection == null) {
             // Can happen for single process.
             return;
@@ -606,7 +606,8 @@
 
     @VisibleForTesting
     public static void setSandboxServicesSettingsForTesting(
-            BaseChildProcessConnection.Factory factory, int serviceCount, String serviceName) {
+            ChildConnectionAllocator.ConnectionFactory factory, int serviceCount,
+            String serviceName) {
         sSandboxedServiceFactoryForTesting = factory;
         sSandboxedServicesCountForTesting = serviceCount;
         sSandboxedServicesNameForTesting = serviceName;
@@ -621,7 +622,7 @@
         if (sServiceMap.get(pid) == null) return false;
 
         try {
-            ((ManagedChildProcessConnection) sServiceMap.get(pid)).crashServiceForTesting();
+            sServiceMap.get(pid).crashServiceForTesting();
         } catch (RemoteException ex) {
             return false;
         }
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java
index 3ff2afc..d810afd 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java
@@ -30,7 +30,7 @@
 
     // Note native pointer is only guaranteed live until nativeOnChildProcessStarted.
     private long mNativeChildProcessLauncherHelper;
-    private BaseChildProcessConnection mChildProcessConnection;
+    private ChildProcessConnection mChildProcessConnection;
 
     @CalledByNative
     private static FileDescriptorInfo makeFdInfo(
@@ -66,7 +66,7 @@
         ChildProcessLauncher.start(ContextUtils.getApplicationContext(), paramId, commandLine,
                 filesToBeMapped, new ChildProcessLauncher.LaunchCallback() {
                     @Override
-                    public void onChildProcessStarted(BaseChildProcessConnection connection) {
+                    public void onChildProcessStarted(ChildProcessConnection connection) {
                         mChildProcessConnection = connection;
                         if (mNativeChildProcessLauncherHelper != 0) {
                             nativeOnChildProcessStarted(
@@ -91,15 +91,10 @@
             return false;
         }
 
-        if (mChildProcessConnection instanceof ImportantChildProcessConnection) {
-            // The connection was bound as BIND_IMPORTANT. This should prevent it from being killed
-            // when the app is on the foreground (that's our best guess, but there is no absolute
-            // guarantee).
-            return ChildProcessLauncher.isApplicationInForeground();
-        }
-
-        return ((ManagedChildProcessConnection) mChildProcessConnection)
-                .isOomProtectedOrWasWhenDied();
+        // We consider the process to be child protected if it has a strong or moderate binding and
+        // the app is in the foreground.
+        return ChildProcessLauncher.isApplicationInForeground()
+                && !mChildProcessConnection.isWaivedBoundOnlyOrWasWhenDied();
     }
 
     @CalledByNative
diff --git a/content/public/android/java/src/org/chromium/content/browser/ImportantChildProcessConnection.java b/content/public/android/java/src/org/chromium/content/browser/ImportantChildProcessConnection.java
deleted file mode 100644
index 700f00af..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/ImportantChildProcessConnection.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import org.chromium.base.process_launcher.ChildProcessCreationParams;
-
-/**
- * A connection that is bound as important, meaning the framework brings it to the foreground
- * process level when the app is.
- */
-public class ImportantChildProcessConnection extends BaseChildProcessConnection {
-    public static final Factory FACTORY = new BaseChildProcessConnection.Factory() {
-        @Override
-        public BaseChildProcessConnection create(Context context, DeathCallback deathCallback,
-                String serviceClassName, Bundle childProcessCommonParameters,
-                ChildProcessCreationParams creationParams) {
-            return new ImportantChildProcessConnection(context, deathCallback, serviceClassName,
-                    childProcessCommonParameters, creationParams);
-        }
-    };
-
-    private final ChildServiceConnection mBinding;
-
-    private ImportantChildProcessConnection(Context context, DeathCallback deathCallback,
-            String serviceClassName, Bundle childProcessCommonParameters,
-            ChildProcessCreationParams creationParams) {
-        super(context, deathCallback, serviceClassName, childProcessCommonParameters,
-                creationParams);
-        int flags = Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT;
-        if (shouldBindAsExportedService()) {
-            flags |= Context.BIND_EXTERNAL_SERVICE;
-        }
-        mBinding = createServiceConnection(flags);
-    }
-
-    @Override
-    public boolean bind() {
-        return mBinding.bind();
-    }
-
-    @Override
-    public void unbind() {
-        mBinding.unbind();
-    }
-}
diff --git a/content/public/android/java/src/org/chromium/content/browser/ManagedChildProcessConnection.java b/content/public/android/java/src/org/chromium/content/browser/ManagedChildProcessConnection.java
deleted file mode 100644
index 0726181..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/ManagedChildProcessConnection.java
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.base.process_launcher.ChildProcessCreationParams;
-
-/**
- * ManagedChildProcessConnection is a connection to a child service that can hold several bindings
- * to the service so it can be more or less agressively protected against OOM.
- * Accessed from the launcher thread. (but for isOomProtectedOrWasWhenDied()).
- */
-public class ManagedChildProcessConnection extends BaseChildProcessConnection {
-    private static final String TAG = "ManChildProcessConn";
-
-    public static final Factory FACTORY = new BaseChildProcessConnection.Factory() {
-        @Override
-        public BaseChildProcessConnection create(Context context, DeathCallback deathCallback,
-                String serviceClassName, Bundle childProcessCommonParameters,
-                ChildProcessCreationParams creationParams) {
-            assert LauncherThread.runningOnLauncherThread();
-            return new ManagedChildProcessConnection(context, deathCallback, serviceClassName,
-                    childProcessCommonParameters, creationParams);
-        }
-    };
-
-    // Initial binding protects the newly spawned process from being killed before it is put to use,
-    // it is maintained between calls to start() and removeInitialBinding().
-    private final ChildServiceConnection mInitialBinding;
-
-    // Strong binding will make the service priority equal to the priority of the activity. We want
-    // the OS to be able to kill background renderers as it kills other background apps, so strong
-    // bindings are maintained only for services that are active at the moment (between
-    // addStrongBinding() and removeStrongBinding()).
-    private final ChildServiceConnection mStrongBinding;
-
-    // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls
-    // to start() and stop().
-    private final ChildServiceConnection mWaivedBinding;
-
-    // Incremented on addStrongBinding(), decremented on removeStrongBinding().
-    private int mStrongBindingCount;
-
-    // Moderate binding will make the service priority equal to the priority of a visible process
-    // while the app is in the foreground. It will stay bound only while the app is in the
-    // foreground to protect a background process from the system out-of-memory killer.
-    private final ChildServiceConnection mModerateBinding;
-
-    // Indicates whether the connection is OOM protected (if the connection is unbound, it contains
-    // the state at time of unbinding).
-    private boolean mOomProtected;
-
-    // Set to true once unbind() was called.
-    private boolean mUnbound;
-
-    @VisibleForTesting
-    ManagedChildProcessConnection(Context context, DeathCallback deathCallback,
-            String serviceClassName, Bundle childProcessCommonParameters,
-            ChildProcessCreationParams creationParams) {
-        super(context, deathCallback, serviceClassName, childProcessCommonParameters,
-                creationParams);
-
-        int initialFlags = Context.BIND_AUTO_CREATE;
-        int extraBindFlags = shouldBindAsExportedService() ? Context.BIND_EXTERNAL_SERVICE : 0;
-        mInitialBinding = createServiceConnection(initialFlags | extraBindFlags);
-        mStrongBinding = createServiceConnection(
-                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT | extraBindFlags);
-        mWaivedBinding = createServiceConnection(
-                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | extraBindFlags);
-        mModerateBinding = createServiceConnection(Context.BIND_AUTO_CREATE | extraBindFlags);
-    }
-
-    @Override
-    protected boolean bind() {
-        assert LauncherThread.runningOnLauncherThread();
-        assert !mUnbound;
-        if (!mInitialBinding.bind()) {
-            return false;
-        }
-        updateOomProtectedState();
-        mWaivedBinding.bind();
-        return true;
-    }
-
-    @Override
-    public void unbind() {
-        assert LauncherThread.runningOnLauncherThread();
-        mUnbound = true;
-        mInitialBinding.unbind();
-        mStrongBinding.unbind();
-        // Note that we don't update the OOM state here as to preserve the last OOM state.
-        mWaivedBinding.unbind();
-        mModerateBinding.unbind();
-        mStrongBindingCount = 0;
-    }
-
-    public boolean isInitialBindingBound() {
-        assert LauncherThread.runningOnLauncherThread();
-        return mInitialBinding.isBound();
-    }
-
-    public boolean isStrongBindingBound() {
-        assert LauncherThread.runningOnLauncherThread();
-        return mStrongBinding.isBound();
-    }
-
-    public void addInitialBinding() {
-        assert LauncherThread.runningOnLauncherThread();
-        mInitialBinding.bind();
-        updateOomProtectedState();
-    }
-
-    public void removeInitialBinding() {
-        assert LauncherThread.runningOnLauncherThread();
-        mInitialBinding.unbind();
-        updateOomProtectedState();
-    }
-
-    /**
-     * @return true if the connection is bound and OOM protected or was OOM protected when unbound.
-     */
-    public boolean isOomProtectedOrWasWhenDied() {
-        // WARNING: this method can be called from a thread other than the launcher thread.
-        // Note that it returns the current OOM protected state and is racy. This not really
-        // preventable without changing the caller's API, short of blocking.
-        return mOomProtected;
-    }
-
-    public void dropOomBindings() {
-        assert LauncherThread.runningOnLauncherThread();
-        mInitialBinding.unbind();
-
-        mStrongBindingCount = 0;
-        mStrongBinding.unbind();
-        updateOomProtectedState();
-
-        mModerateBinding.unbind();
-    }
-
-    public void addStrongBinding() {
-        assert LauncherThread.runningOnLauncherThread();
-        if (!isConnected()) {
-            Log.w(TAG, "The connection is not bound for %d", getPid());
-            return;
-        }
-        if (mStrongBindingCount == 0) {
-            mStrongBinding.bind();
-            updateOomProtectedState();
-        }
-        mStrongBindingCount++;
-    }
-
-    public void removeStrongBinding() {
-        assert LauncherThread.runningOnLauncherThread();
-        if (!isConnected()) {
-            Log.w(TAG, "The connection is not bound for %d", getPid());
-            return;
-        }
-        assert mStrongBindingCount > 0;
-        mStrongBindingCount--;
-        if (mStrongBindingCount == 0) {
-            mStrongBinding.unbind();
-            updateOomProtectedState();
-        }
-        updateOomProtectedState();
-    }
-
-    public boolean isModerateBindingBound() {
-        assert LauncherThread.runningOnLauncherThread();
-        return mModerateBinding.isBound();
-    }
-
-    public void addModerateBinding() {
-        assert LauncherThread.runningOnLauncherThread();
-        if (!isConnected()) {
-            Log.w(TAG, "The connection is not bound for %d", getPid());
-            return;
-        }
-        mModerateBinding.bind();
-    }
-
-    public void removeModerateBinding() {
-        assert LauncherThread.runningOnLauncherThread();
-        if (!isConnected()) {
-            Log.w(TAG, "The connection is not bound for %d", getPid());
-            return;
-        }
-        mModerateBinding.unbind();
-    }
-
-    // Should be called every time the mInitialBinding or mStrongBinding are bound/unbound.
-    private void updateOomProtectedState() {
-        if (!mUnbound) {
-            mOomProtected = mInitialBinding.isBound() || mStrongBinding.isBound();
-        }
-    }
-}
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherIntegrationTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherIntegrationTest.java
index 7e857db4..974f648 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherIntegrationTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherIntegrationTest.java
@@ -37,18 +37,38 @@
     public final ContentShellActivityTestRule mActivityTestRule =
             new ContentShellActivityTestRule();
 
-    private static class TestManagedChildProcessConnection extends ManagedChildProcessConnection {
+    private static class TestChildProcessConnectionFactory
+            implements ChildConnectionAllocator.ConnectionFactory {
+        private final List<TestChildProcessConnection> mConnections = new ArrayList<>();
+
+        @Override
+        public ChildProcessConnection createConnection(ChildSpawnData spawnData,
+                ChildProcessConnection.DeathCallback deathCallback,
+                Bundle childProcessCommonParameters, String serviceClassName) {
+            TestChildProcessConnection connection = new TestChildProcessConnection(
+                    spawnData.getContext(), deathCallback, serviceClassName,
+                    childProcessCommonParameters, spawnData.getCreationParams());
+            mConnections.add(connection);
+            return connection;
+        }
+
+        public List<TestChildProcessConnection> getConnections() {
+            return mConnections;
+        }
+    }
+
+    private static class TestChildProcessConnection extends ChildProcessConnection {
         private RuntimeException mRemovedBothInitialAndStrongBinding;
 
-        public TestManagedChildProcessConnection(Context context,
-                BaseChildProcessConnection.DeathCallback deathCallback, String serviceClassName,
+        public TestChildProcessConnection(Context context,
+                ChildProcessConnection.DeathCallback deathCallback, String serviceClassName,
                 Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams) {
             super(context, deathCallback, serviceClassName, childProcessCommonParameters,
                     creationParams);
         }
 
         @Override
-        public void unbind() {
+        protected void unbind() {
             super.unbind();
             if (mRemovedBothInitialAndStrongBinding == null) {
                 mRemovedBothInitialAndStrongBinding = new RuntimeException("unbind");
@@ -86,31 +106,11 @@
         }
     }
 
-    private static class TestChildProcessConnectionFactory
-            implements BaseChildProcessConnection.Factory {
-        private final List<TestManagedChildProcessConnection> mConnections = new ArrayList<>();
-
-        @Override
-        public BaseChildProcessConnection create(Context context,
-                BaseChildProcessConnection.DeathCallback deathCallback, String serviceClassName,
-                Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams) {
-            TestManagedChildProcessConnection connection =
-                    new TestManagedChildProcessConnection(context, deathCallback, serviceClassName,
-                            childProcessCommonParameters, creationParams);
-            mConnections.add(connection);
-            return connection;
-        }
-
-        public List<TestManagedChildProcessConnection> getConnections() {
-            return mConnections;
-        }
-    }
-
     @Test
     @MediumTest
     public void testCrossDomainNavigationDoNotLoseImportance() throws Throwable {
         final TestChildProcessConnectionFactory factory = new TestChildProcessConnectionFactory();
-        final List<TestManagedChildProcessConnection> connections = factory.getConnections();
+        final List<TestChildProcessConnection> connections = factory.getConnections();
         ChildProcessLauncher.setSandboxServicesSettingsForTesting(factory,
                 10 /* arbitrary number, only realy need 2 */, null /* use default service name */);
 
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
index 1a9fbec..fca55b4 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
@@ -103,7 +103,7 @@
         Assert.assertEquals(0, ChildProcessLauncher.connectedServicesCountForTesting());
 
         // Start and connect to a new service.
-        final BaseChildProcessConnection connection = startConnection();
+        final ChildProcessConnection connection = startConnection();
         Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
 
         // Verify that the service is not yet set up.
@@ -139,7 +139,7 @@
         Assert.assertEquals(0, allocatedChromeSandboxedConnectionsCount());
 
         // Start and connect to a new service.
-        final BaseChildProcessConnection connection = startConnection();
+        final ChildProcessConnection connection = startConnection();
         Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
 
         // Initiate the connection setup.
@@ -194,7 +194,7 @@
         Assert.assertEquals(0, allocatedChromeSandboxedConnectionsCount());
 
         // Start and connect to a new service.
-        final BaseChildProcessConnection connection = startConnection();
+        final ChildProcessConnection connection = startConnection();
         Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
 
         // Queue up a new spawn request. There is no way to kill the pending connection, leak it
@@ -270,10 +270,10 @@
         Assert.assertEquals(0, allocatedChromeSandboxedConnectionsCount());
 
         // Start and connect to a new service of an external APK.
-        BaseChildProcessConnection externalApkConnection =
+        ChildProcessConnection externalApkConnection =
                 allocateConnection(EXTERNAL_APK_PACKAGE_NAME);
         // Start and connect to a new service for a regular tab.
-        BaseChildProcessConnection tabConnection = allocateConnection(appContext.getPackageName());
+        ChildProcessConnection tabConnection = allocateConnection(appContext.getPackageName());
 
         // Verify that one connection is allocated for an external APK and a regular tab
         // respectively.
@@ -308,17 +308,17 @@
                         appContext, EXTERNAL_APK_PACKAGE_NAME));
 
         // Setup a connection for an external APK to reach the maximum allowed connection number.
-        BaseChildProcessConnection externalApkConnection =
+        ChildProcessConnection externalApkConnection =
                 allocateConnection(EXTERNAL_APK_PACKAGE_NAME);
         Assert.assertNotNull(externalApkConnection);
 
         // Verify that there isn't any connection available for the external APK.
-        BaseChildProcessConnection exceedNumberExternalApkConnection =
+        ChildProcessConnection exceedNumberExternalApkConnection =
                 allocateConnection(EXTERNAL_APK_PACKAGE_NAME);
         Assert.assertNull(exceedNumberExternalApkConnection);
 
         // Verify that we can still allocate connection for a regular tab.
-        BaseChildProcessConnection tabConnection = allocateConnection(appContext.getPackageName());
+        ChildProcessConnection tabConnection = allocateConnection(appContext.getPackageName());
         Assert.assertNotNull(tabConnection);
     }
 
@@ -408,9 +408,8 @@
         final ChildProcessCreationParams creationParams = new ChildProcessCreationParams(
                 context.getPackageName(), false /* isExternalService */,
                 LibraryProcessType.PROCESS_CHILD, true /* bindToCallerCheck */);
-        final BaseChildProcessConnection conn =
-                ChildProcessLauncherTestUtils.startInternalForTesting(
-                        context, sProcessWaitArguments, new FileDescriptorInfo[0], creationParams);
+        final ChildProcessConnection conn = ChildProcessLauncherTestUtils.startInternalForTesting(
+                context, sProcessWaitArguments, new FileDescriptorInfo[0], creationParams);
 
         CriteriaHelper.pollInstrumentationThread(
                 new Criteria("Failed waiting for instrumentation-bound service") {
@@ -422,7 +421,7 @@
 
         Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionServiceNumber(conn));
 
-        final BaseChildProcessConnection[] sandboxedConnections =
+        final ChildProcessConnection[] sandboxedConnections =
                 getSandboxedConnectionArrayForTesting(context, context.getPackageName());
 
         // Wait for the retry to succeed.
@@ -432,7 +431,7 @@
                     public boolean isSatisfied() {
                         boolean allChildrenConnected = true;
                         for (int i = 0; i <= 1; ++i) {
-                            BaseChildProcessConnection conn = sandboxedConnections[i];
+                            ChildProcessConnection conn = sandboxedConnections[i];
                             allChildrenConnected &= conn != null
                                     && ChildProcessLauncherTestUtils.getConnectionService(conn)
                                             != null;
@@ -443,7 +442,7 @@
 
         // Check that only two connections are created.
         for (int i = 0; i < sandboxedConnections.length; ++i) {
-            BaseChildProcessConnection sandboxedConn = sandboxedConnections[i];
+            ChildProcessConnection sandboxedConn = sandboxedConnections[i];
             if (i <= 1) {
                 Assert.assertNotNull(sandboxedConn);
                 Assert.assertNotNull(
@@ -454,7 +453,7 @@
         }
 
         Assert.assertTrue(conn == sandboxedConnections[0]);
-        final BaseChildProcessConnection retryConn = sandboxedConnections[1];
+        final ChildProcessConnection retryConn = sandboxedConnections[1];
 
         Assert.assertFalse(conn == retryConn);
 
@@ -496,7 +495,7 @@
             public void run() {
                 Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
 
-                final BaseChildProcessConnection conn =
+                final ChildProcessConnection conn =
                         ChildProcessLauncherTestUtils.startInternalForTesting(
                                 context, new String[0], new FileDescriptorInfo[0], null);
                 Assert.assertEquals(
@@ -538,10 +537,10 @@
         });
     }
 
-    private BaseChildProcessConnection startConnection() {
+    private ChildProcessConnection startConnection() {
         // Allocate a new connection.
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        final BaseChildProcessConnection connection = allocateBoundConnectionForTesting(
+        final ChildProcessConnection connection = allocateBoundConnectionForTesting(
                 context, getDefaultChildProcessCreationParams(context.getPackageName()));
 
         // Wait for the service to connect.
@@ -564,12 +563,12 @@
                 filesToMap, null /* launchCallback */);
     }
 
-    private static BaseChildProcessConnection allocateBoundConnectionForTesting(
+    private static ChildProcessConnection allocateBoundConnectionForTesting(
             final Context context, final ChildProcessCreationParams creationParams) {
         return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
-                new Callable<BaseChildProcessConnection>() {
+                new Callable<ChildProcessConnection>() {
                     @Override
-                    public BaseChildProcessConnection call() {
+                    public ChildProcessConnection call() {
                         return ChildProcessLauncher.allocateBoundConnection(
                                 new ChildSpawnData(context, null /* commandLine */,
                                         null /* filesToBeMapped */, null /* LaunchCallback */,
@@ -585,11 +584,11 @@
      * but doesn't really start the connection to bind a service. It is for testing whether the
      * connection is allocated properly for different application packages.
      */
-    private BaseChildProcessConnection allocateConnection(final String packageName) {
+    private ChildProcessConnection allocateConnection(final String packageName) {
         return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
-                new Callable<BaseChildProcessConnection>() {
+                new Callable<ChildProcessConnection>() {
                     @Override
-                    public BaseChildProcessConnection call() {
+                    public ChildProcessConnection call() {
                         // Allocate a new connection.
                         Context context = InstrumentationRegistry.getTargetContext();
                         ChildProcessCreationParams creationParams =
@@ -636,12 +635,12 @@
                 });
     }
 
-    private static BaseChildProcessConnection[] getSandboxedConnectionArrayForTesting(
+    private static ChildProcessConnection[] getSandboxedConnectionArrayForTesting(
             final Context context, final String packageName) {
         return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
-                new Callable<BaseChildProcessConnection[]>() {
+                new Callable<ChildProcessConnection[]>() {
                     @Override
-                    public BaseChildProcessConnection[] call() {
+                    public ChildProcessConnection[] call() {
                         return ChildProcessLauncher
                                 .getConnectionAllocator(context, packageName, true /*isSandboxed */)
                                 .connectionArrayForTesting();
@@ -675,13 +674,13 @@
                 LibraryProcessType.PROCESS_CHILD, false /* bindToCallerCheck */);
     }
 
-    private void triggerConnectionSetup(final BaseChildProcessConnection connection) {
+    private void triggerConnectionSetup(final ChildProcessConnection connection) {
         ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
             @Override
             public void run() {
                 ChildProcessLauncher.triggerConnectionSetup(connection, sProcessWaitArguments,
                         new FileDescriptorInfo[0], null /* launchCallback */,
-                        null /* childProcessCallback */);
+                        null /* childProcessCallback */, true /* addToBindingManager */);
             }
         });
     }
diff --git a/content/public/android/junit/src/org/chromium/content/browser/BindingManagerImplTest.java b/content/public/android/junit/src/org/chromium/content/browser/BindingManagerImplTest.java
index b1a4f9f..74d789ff 100644
--- a/content/public/android/junit/src/org/chromium/content/browser/BindingManagerImplTest.java
+++ b/content/public/android/junit/src/org/chromium/content/browser/BindingManagerImplTest.java
@@ -28,8 +28,7 @@
 import java.util.ArrayList;
 
 /**
- * Unit tests for BindingManagerImpl. The tests run agains mock ChildProcessConnection
- * implementation, thus testing only the BindingManagerImpl itself.
+ * Unit tests for BindingManagerImpl and ChildProcessConnection.
  *
  * Default property of being low-end device is overriden, so that both low-end and high-end policies
  * are tested.
@@ -38,7 +37,7 @@
 @Config(manifest = Config.NONE)
 public class BindingManagerImplTest {
     private static class MockChildServiceConnection
-            implements BaseChildProcessConnection.ChildServiceConnection {
+            implements ChildProcessConnection.ChildServiceConnection {
         private boolean mBound;
 
         @Override
@@ -58,7 +57,7 @@
         }
     }
 
-    private static class TestChildProcessConnection extends ManagedChildProcessConnection {
+    private static class TestChildProcessConnection extends ChildProcessConnection {
         private final int mPid;
         private boolean mConnected;
 
@@ -87,8 +86,8 @@
 
         // We don't have a real service so we have to mock the connection status.
         @Override
-        public void start(StartCallback startCallback) {
-            super.start(startCallback);
+        public void start(boolean useStrongBinding, StartCallback startCallback) {
+            super.start(useStrongBinding, startCallback);
             mConnected = true;
         }
 
@@ -104,6 +103,17 @@
         }
     }
 
+    // Creates a mocked ChildProcessConnection that is optionally added to a BindingManager.
+    private static ChildProcessConnection createTestChildProcessConnection(
+            int pid, BindingManager manager) {
+        ChildProcessConnection connection = new TestChildProcessConnection(pid);
+        connection.start(false /* useStrongBinding */, null /* startCallback */);
+        if (manager != null) {
+            manager.addNewConnection(pid, connection);
+        }
+        return connection;
+    }
+
     /**
      * Helper class that stores a manager along with its text label. This is used for tests that
      * iterate over all managers to indicate which manager was being tested when an assertion
@@ -168,18 +178,16 @@
         BindingManagerImpl manager = mLowEndManager;
 
         // Add a connection to the manager.
-        TestChildProcessConnection firstConnection = new TestChildProcessConnection(1);
-        firstConnection.start(null /* startCallback */);
-        manager.addNewConnection(firstConnection.getPid(), firstConnection);
+        ChildProcessConnection firstConnection =
+                createTestChildProcessConnection(1 /* pid */, manager);
 
         // Bind a strong binding on the connection.
         manager.setPriority(firstConnection.getPid(), true /* foreground */, false /* boost */);
         Assert.assertTrue(firstConnection.isStrongBindingBound());
 
         // Add a new connection.
-        TestChildProcessConnection secondConnection = new TestChildProcessConnection(2);
-        secondConnection.start(null /* startCallback */);
-        manager.addNewConnection(secondConnection.getPid(), secondConnection);
+        ChildProcessConnection secondConnection =
+                createTestChildProcessConnection(2 /* pid */, manager);
 
         // Verify that the strong binding for the first connection wasn't dropped.
         Assert.assertTrue(firstConnection.isStrongBindingBound());
@@ -188,6 +196,7 @@
         // got used in foreground.
         manager.setPriority(secondConnection.getPid(), true /* foreground */, false /* boost */);
         Assert.assertFalse(firstConnection.isStrongBindingBound());
+        Assert.assertTrue(secondConnection.isStrongBindingBound());
     }
 
     /**
@@ -198,12 +207,11 @@
     @Feature({"ProcessManagement"})
     public void testStrongBindingRemovalOnLowEnd() throws Throwable {
         // This test applies only to the low-end manager.
-        final BindingManagerImpl manager = mLowEndManager;
+        BindingManagerImpl manager = mLowEndManager;
 
         // Add a connection to the manager.
-        final TestChildProcessConnection connection = new TestChildProcessConnection(1);
-        connection.start(null /* startCallback */);
-        manager.addNewConnection(connection.getPid(), connection);
+        ChildProcessConnection connection = createTestChildProcessConnection(1 /* pid */, manager);
+        Assert.assertTrue(connection.isInitialBindingBound());
         Assert.assertFalse(connection.isStrongBindingBound());
 
         // Add a strong binding.
@@ -223,12 +231,11 @@
     @Feature({"ProcessManagement"})
     public void testStrongBindingRemovalOnHighEnd() throws Throwable {
         // This test applies only to the high-end manager.
-        final BindingManagerImpl manager = mHighEndManager;
+        BindingManagerImpl manager = mHighEndManager;
 
         // Add a connection to the manager.
-        final TestChildProcessConnection connection = new TestChildProcessConnection(1);
-        connection.start(null /* startCallback */);
-        manager.addNewConnection(connection.getPid(), connection);
+        ChildProcessConnection connection = createTestChildProcessConnection(1 /* pid */, manager);
+        Assert.assertTrue(connection.isInitialBindingBound());
         Assert.assertFalse(connection.isStrongBindingBound());
 
         // Add a strong binding, verify that the initial binding is not removed.
@@ -254,12 +261,10 @@
     @Feature({"ProcessManagement"})
     public void testStrongBindingRemovalWithModerateBinding() throws Throwable {
         // This test applies only to the moderate-binding manager.
-        final BindingManagerImpl manager = mModerateBindingManager;
+        BindingManagerImpl manager = mModerateBindingManager;
 
         // Add a connection to the manager and start it.
-        final TestChildProcessConnection connection = new TestChildProcessConnection(1);
-        connection.start(null /* startCallback */);
-        manager.addNewConnection(connection.getPid(), connection);
+        ChildProcessConnection connection = createTestChildProcessConnection(1 /* pid */, manager);
 
         Assert.assertTrue(connection.isInitialBindingBound());
         Assert.assertFalse(connection.isStrongBindingBound());
@@ -285,33 +290,37 @@
 
     /**
      * This test corresponds to a process crash scenario: after a process dies and its connection is
-     * cleared, isOomProtectedOrWasWhenDied() may be called on the connection to decide if it was a
-     * crash or out-of-memory kill.
+     * cleared, isWaivedBoundOnlyOrWasWhenDied() may be called on the connection to decide if it was
+     * a crash or out-of-memory kill.
      */
     @Test
     @Feature({"ProcessManagement"})
-    public void testIsOomProtected() {
+    public void testIsWaivedBoundOnly() {
         // This test applies to low-end, high-end and moderate-binding policies.
         for (ManagerEntry managerEntry : mAllManagers) {
             BindingManagerImpl manager = managerEntry.mManager;
             String message = managerEntry.getErrorMessage();
 
             // Add a connection to the manager.
-            TestChildProcessConnection connection = new TestChildProcessConnection(1);
-            connection.start(null /* startCallback */);
-            manager.addNewConnection(connection.getPid(), connection);
+            ChildProcessConnection connection =
+                    createTestChildProcessConnection(1 /* pid */, manager);
 
-            // Initial binding is an oom binding.
-            Assert.assertTrue(message, connection.isOomProtectedOrWasWhenDied());
+            // Initial binding is a moderate binding.
+            Assert.assertFalse(message, connection.isWaivedBoundOnlyOrWasWhenDied());
 
-            // After initial binding is removed, the connection is no longer oom protected.
+            // After initial binding is removed, the connection is no longer waived bound only.
             manager.setPriority(connection.getPid(), false /* foreground */, false /* boost */);
             ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
-            Assert.assertFalse(message, connection.isOomProtectedOrWasWhenDied());
+            if (managerEntry.mManager == mModerateBindingManager) {
+                // The moderate binding manager adds a moderate binding.
+                Assert.assertFalse(message, connection.isWaivedBoundOnlyOrWasWhenDied());
+            } else {
+                Assert.assertTrue(message, connection.isWaivedBoundOnlyOrWasWhenDied());
+            }
 
-            // Add a strong binding, restoring the oom protection.
+            // Add a strong binding.
             manager.setPriority(connection.getPid(), true /* foreground */, false /* boost */);
-            Assert.assertTrue(message, connection.isOomProtectedOrWasWhenDied());
+            Assert.assertFalse(message, connection.isWaivedBoundOnlyOrWasWhenDied());
 
             // Simulate a process crash - clear a connection in binding manager and remove the
             // bindings.
@@ -320,11 +329,11 @@
             Assert.assertTrue(manager.isConnectionCleared(connection.getPid()));
             connection.stop();
 
-            // Verify that the connection doesn't keep any oom bindings, but the manager reports the
-            // oom status as protected.
+            // Verify that manager reports the the connection was waived bound.
             Assert.assertFalse(message, connection.isInitialBindingBound());
+            Assert.assertFalse(message, connection.isModerateBindingBound());
             Assert.assertFalse(message, connection.isStrongBindingBound());
-            Assert.assertTrue(message, connection.isOomProtectedOrWasWhenDied());
+            Assert.assertFalse(message, connection.isWaivedBoundOnlyOrWasWhenDied());
         }
     }
 
@@ -347,25 +356,22 @@
             String message = managerEntry.getErrorMessage();
 
             // Add two connections, bind and release each.
-            TestChildProcessConnection firstConnection = new TestChildProcessConnection(1);
-            firstConnection.start(null /* startCallback */);
-            manager.addNewConnection(firstConnection.getPid(), firstConnection);
+            ChildProcessConnection firstConnection =
+                    createTestChildProcessConnection(1 /* pid */, manager);
             manager.setPriority(firstConnection.getPid(), true /* foreground */, false /* boost */);
             manager.setPriority(
                     firstConnection.getPid(), false /* foreground */, false /* boost */);
 
-            TestChildProcessConnection secondConnection = new TestChildProcessConnection(2);
-            secondConnection.start(null /* startCallback */);
-            manager.addNewConnection(secondConnection.getPid(), secondConnection);
+            ChildProcessConnection secondConnection =
+                    createTestChildProcessConnection(2 /* pid */, manager);
             manager.setPriority(
                     secondConnection.getPid(), true /* foreground */, false /* boost */);
             manager.setPriority(
                     secondConnection.getPid(), false /* foreground */, false /* boost */);
 
             // Add third connection, do not bind it.
-            TestChildProcessConnection thirdConnection = new TestChildProcessConnection(3);
-            thirdConnection.start(null /* startCallback */);
-            manager.addNewConnection(thirdConnection.getPid(), thirdConnection);
+            ChildProcessConnection thirdConnection =
+                    createTestChildProcessConnection(3 /* pid */, manager);
             manager.setPriority(
                     thirdConnection.getPid(), false /* foreground */, false /* boost */);
 
@@ -403,16 +409,14 @@
         // This test applies only to the moderate-binding manager.
         final BindingManagerImpl manager = mModerateBindingManager;
 
-        TestChildProcessConnection[] connections = new TestChildProcessConnection[3];
+        ChildProcessConnection[] connections = new ChildProcessConnection[3];
         for (int i = 0; i < connections.length; i++) {
-            connections[i] = new TestChildProcessConnection(i + 1);
-            connections[i].start(null /* startCallback */);
-            manager.addNewConnection(connections[i].getPid(), connections[i]);
+            connections[i] = createTestChildProcessConnection(i + 1 /* pid */, manager);
         }
 
         // Verify that each connection has a moderate binding after binding and releasing a strong
         // binding.
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             manager.setPriority(connection.getPid(), true /* foreground */, false /* boost */);
             manager.setPriority(connection.getPid(), false /* foreground */, false /* boost */);
             ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
@@ -421,9 +425,8 @@
 
         // Exclude lastInForeground because it will be kept in foreground when onSentToBackground()
         // is called as |mLastInForeground|.
-        TestChildProcessConnection lastInForeground = new TestChildProcessConnection(0);
-        manager.addNewConnection(lastInForeground.getPid(), lastInForeground);
-        lastInForeground.start(null /* startCallback */);
+        ChildProcessConnection lastInForeground =
+                createTestChildProcessConnection(0 /* pid */, manager);
         manager.setPriority(lastInForeground.getPid(), true /* foreground */, false /* boost */);
         manager.setPriority(lastInForeground.getPid(), false /* foreground */, false /* boost */);
 
@@ -431,34 +434,34 @@
 
         // Verify that leaving the application for a short time doesn't clear the moderate bindings.
         manager.onSentToBackground();
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             Assert.assertTrue(connection.isModerateBindingBound());
         }
         Assert.assertTrue(lastInForeground.isStrongBindingBound());
         Assert.assertFalse(lastInForeground.isModerateBindingBound());
         manager.onBroughtToForeground();
         ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             Assert.assertTrue(connection.isModerateBindingBound());
         }
 
         // Call onSentToBackground() and verify that all the moderate bindings drop after some
         // delay.
         manager.onSentToBackground();
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             Assert.assertTrue(connection.isModerateBindingBound());
         }
         Assert.assertTrue(lastInForeground.isStrongBindingBound());
         Assert.assertFalse(lastInForeground.isModerateBindingBound());
         ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             Assert.assertFalse(connection.isModerateBindingBound());
         }
 
         // Call onBroughtToForeground() and verify that the previous moderate bindings aren't
         // recovered.
         manager.onBroughtToForeground();
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             Assert.assertFalse(connection.isModerateBindingBound());
         }
     }
@@ -472,16 +475,14 @@
         final Application app = mActivity.getApplication();
         final BindingManagerImpl manager = mModerateBindingManager;
 
-        TestChildProcessConnection[] connections = new TestChildProcessConnection[4];
+        ChildProcessConnection[] connections = new ChildProcessConnection[4];
         for (int i = 0; i < connections.length; i++) {
-            connections[i] = new TestChildProcessConnection(i + 1);
-            connections[i].start(null /* startCallback */);
-            manager.addNewConnection(connections[i].getPid(), connections[i]);
+            connections[i] = createTestChildProcessConnection(i + 1 /* pid */, manager);
         }
 
         // Verify that each connection has a moderate binding after binding and releasing a strong
         // binding.
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             manager.setPriority(connection.getPid(), true /* foreground */, false /* boost */);
             manager.setPriority(connection.getPid(), false /* foreground */, false /* boost */);
             ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
@@ -490,7 +491,7 @@
 
         // Call onLowMemory() and verify that all the moderate bindings drop.
         app.onLowMemory();
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             Assert.assertFalse(connection.isModerateBindingBound());
         }
     }
@@ -512,18 +513,16 @@
         levelAndExpectedVictimCountList.add(
                 new Pair<Integer, Integer>(TRIM_MEMORY_RUNNING_CRITICAL, 4));
 
-        TestChildProcessConnection[] connections = new TestChildProcessConnection[4];
+        ChildProcessConnection[] connections = new ChildProcessConnection[4];
         for (int i = 0; i < connections.length; i++) {
-            connections[i] = new TestChildProcessConnection(i + 1);
-            connections[i].start(null /* startCallback */);
-            manager.addNewConnection(connections[i].getPid(), connections[i]);
+            connections[i] = createTestChildProcessConnection(i + 1 /* pid */, manager);
         }
 
         for (Pair<Integer, Integer> pair : levelAndExpectedVictimCountList) {
             String message = "Failed for the level=" + pair.first;
             // Verify that each connection has a moderate binding after binding and releasing a
             // strong binding.
-            for (TestChildProcessConnection connection : connections) {
+            for (ChildProcessConnection connection : connections) {
                 manager.setPriority(connection.getPid(), true /* foreground */, false /* boost */);
                 manager.setPriority(connection.getPid(), false /* foreground */, false /* boost */);
                 ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
@@ -548,16 +547,14 @@
         // This test applies only to the moderate-binding manager.
         final BindingManagerImpl manager = mModerateBindingManager;
 
-        TestChildProcessConnection[] connections = new TestChildProcessConnection[4];
+        ChildProcessConnection[] connections = new ChildProcessConnection[4];
         for (int i = 0; i < connections.length; i++) {
-            connections[i] = new TestChildProcessConnection(i + 1);
-            connections[i].start(null /* startCallback */);
-            manager.addNewConnection(connections[i].getPid(), connections[i]);
+            connections[i] = createTestChildProcessConnection(i + 1 /* pid */, manager);
         }
 
         // Verify that each connection has a moderate binding after binding and releasing a strong
         // binding.
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             manager.setPriority(connection.getPid(), true /* foreground */, false /* boost */);
             manager.setPriority(connection.getPid(), false /* foreground */, false /* boost */);
             ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
@@ -567,7 +564,7 @@
         // Call BindingManager.releaseAllModerateBindings() and verify that all the moderate
         // bindings drop.
         manager.releaseAllModerateBindings();
-        for (TestChildProcessConnection connection : connections) {
+        for (ChildProcessConnection connection : connections) {
             Assert.assertFalse(connection.isModerateBindingBound());
         }
     }
@@ -582,9 +579,7 @@
         BindingManagerImpl manager = BindingManagerImpl.createBindingManagerForTesting(false);
         manager.startModerateBindingManagement(mActivity, 4);
 
-        TestChildProcessConnection connection = new TestChildProcessConnection(0);
-        connection.start(null /* startCallback */);
-        manager.addNewConnection(connection.getPid(), connection);
+        ChildProcessConnection connection = createTestChildProcessConnection(0 /* pid */, manager);
         Assert.assertTrue(connection.isInitialBindingBound());
         Assert.assertFalse(connection.isModerateBindingBound());
 
@@ -603,9 +598,7 @@
         BindingManagerImpl manager = BindingManagerImpl.createBindingManagerForTesting(false);
         manager.startModerateBindingManagement(mActivity, 4);
 
-        TestChildProcessConnection connection = new TestChildProcessConnection(0);
-        connection.start(null /* startCallback */);
-        manager.addNewConnection(connection.getPid(), connection);
+        ChildProcessConnection connection = createTestChildProcessConnection(0 /* pid */, manager);
         Assert.assertTrue(connection.isInitialBindingBound());
         Assert.assertFalse(connection.isStrongBindingBound());
         Assert.assertFalse(connection.isModerateBindingBound());
@@ -626,9 +619,7 @@
         BindingManagerImpl manager = BindingManagerImpl.createBindingManagerForTesting(false);
         manager.startModerateBindingManagement(mActivity, 4);
 
-        TestChildProcessConnection connection = new TestChildProcessConnection(0);
-        connection.start(null /* startCallback */);
-        manager.addNewConnection(connection.getPid(), connection);
+        ChildProcessConnection connection = createTestChildProcessConnection(0, manager);
         manager.setPriority(connection.getPid(), false /* foreground */, false /* boost */);
         Assert.assertTrue(connection.isModerateBindingBound());
 
@@ -640,4 +631,4 @@
         manager.onBroughtToForeground();
         Assert.assertFalse(connection.isModerateBindingBound());
     }
-}
+}
\ No newline at end of file
diff --git a/content/public/browser/render_widget_host.h b/content/public/browser/render_widget_host.h
index f2f43504..830df64 100644
--- a/content/public/browser/render_widget_host.h
+++ b/content/public/browser/render_widget_host.h
@@ -25,6 +25,10 @@
 class WebMouseWheelEvent;
 }
 
+namespace ui {
+class LatencyInfo;
+}
+
 namespace content {
 
 struct CursorInfo;
@@ -166,6 +170,9 @@
       const blink::WebMouseWheelEvent& wheel_event) = 0;
   virtual void ForwardKeyboardEvent(
       const NativeWebKeyboardEvent& key_event) = 0;
+  virtual void ForwardKeyboardEventWithLatencyInfo(
+      const NativeWebKeyboardEvent& key_event,
+      const ui::LatencyInfo& latency_info) = 0;
   virtual void ForwardGestureEvent(
       const blink::WebGestureEvent& gesture_event) = 0;
 
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 06089b3..45766502 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -244,7 +244,14 @@
     "VibrateRequiresUserGesture", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables VR UI.
-const base::Feature kVrShell{"VrShell", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kVrShell {
+  "VrShell",
+#if defined(OS_ANDROID)
+      base::FEATURE_ENABLED_BY_DEFAULT
+#else
+      base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
 
 // Enable WebAssembly structured cloning.
 // http://webassembly.org/
diff --git a/content/renderer/OWNERS b/content/renderer/OWNERS
index 502a1a2..d83f872 100644
--- a/content/renderer/OWNERS
+++ b/content/renderer/OWNERS
@@ -1,5 +1,5 @@
 # For Blink API usage
-esprehn@chromium.org
+dglazkov@chromium.org
 
 # For Android
 aelias@chromium.org
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java
index e5b52b8..7687d33 100644
--- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java
+++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java
@@ -20,7 +20,7 @@
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.process_launcher.ChildProcessCreationParams;
 import org.chromium.base.process_launcher.FileDescriptorInfo;
-import org.chromium.content.browser.BaseChildProcessConnection;
+import org.chromium.content.browser.ChildProcessConnection;
 
 /**
  * A Service that assists the ChildProcessLauncherTest that responds to one message, which
@@ -70,9 +70,8 @@
         final boolean bindToCaller = true;
         ChildProcessCreationParams params = new ChildProcessCreationParams(
                 getPackageName(), false, LibraryProcessType.PROCESS_CHILD, bindToCaller);
-        final BaseChildProcessConnection conn =
-                ChildProcessLauncherTestUtils.startInternalForTesting(
-                        this, commandLine, new FileDescriptorInfo[0], params);
+        final ChildProcessConnection conn = ChildProcessLauncherTestUtils.startInternalForTesting(
+                this, commandLine, new FileDescriptorInfo[0], params);
 
         // Poll the connection until it is set up. The main test in ChildProcessLauncherTest, which
         // has bound the connection to this service, manages the timeout via the lifetime of this
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java
index 086dfd6..8bd087c 100644
--- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java
+++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java
@@ -9,7 +9,7 @@
 import org.chromium.base.process_launcher.ChildProcessCreationParams;
 import org.chromium.base.process_launcher.FileDescriptorInfo;
 import org.chromium.base.process_launcher.IChildProcessService;
-import org.chromium.content.browser.BaseChildProcessConnection;
+import org.chromium.content.browser.ChildProcessConnection;
 import org.chromium.content.browser.ChildProcessLauncher;
 import org.chromium.content.browser.LauncherThread;
 
@@ -55,12 +55,12 @@
         }
     }
 
-    public static BaseChildProcessConnection startInternalForTesting(final Context context,
+    public static ChildProcessConnection startInternalForTesting(final Context context,
             final String[] commandLine, final FileDescriptorInfo[] filesToMap,
             final ChildProcessCreationParams params) {
-        return runOnLauncherAndGetResult(new Callable<BaseChildProcessConnection>() {
+        return runOnLauncherAndGetResult(new Callable<ChildProcessConnection>() {
             @Override
-            public BaseChildProcessConnection call() {
+            public ChildProcessConnection call() {
                 return ChildProcessLauncher.startInternal(context, commandLine, filesToMap,
                         null /* launchCallback */, null /* childProcessCallback */,
                         true /* inSandbox */, false /* alwaysInForeground */, params);
@@ -69,7 +69,7 @@
     }
 
     // Retrieves the PID of the passed in connection on the launcher thread as to not assert.
-    public static int getConnectionPid(final BaseChildProcessConnection connection) {
+    public static int getConnectionPid(final ChildProcessConnection connection) {
         return runOnLauncherAndGetResult(new Callable<Integer>() {
             @Override
             public Integer call() {
@@ -80,7 +80,7 @@
 
     // Retrieves the service number of the passed in connection from its service name, or -1 if the
     // service number could not be determined.
-    public static int getConnectionServiceNumber(final BaseChildProcessConnection connection) {
+    public static int getConnectionServiceNumber(final ChildProcessConnection connection) {
         String serviceName = getConnectionServiceName(connection);
         // The service name ends up with the service number.
         StringBuilder numberString = new StringBuilder();
@@ -100,7 +100,7 @@
 
     // Retrieves the service number of the passed in connection on the launcher thread as to not
     // assert.
-    public static String getConnectionServiceName(final BaseChildProcessConnection connection) {
+    public static String getConnectionServiceName(final ChildProcessConnection connection) {
         return runOnLauncherAndGetResult(new Callable<String>() {
             @Override
             public String call() {
@@ -111,7 +111,7 @@
 
     // Retrieves the service of the passed in connection on the launcher thread as to not assert.
     public static IChildProcessService getConnectionService(
-            final BaseChildProcessConnection connection) {
+            final ChildProcessConnection connection) {
         return runOnLauncherAndGetResult(new Callable<IChildProcessService>() {
             @Override
             public IChildProcessService call() {
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
index 873d47b..00b68c7a 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
+++ b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
@@ -1,3 +1,3 @@
 # AUTOGENERATED FILE - DO NOT EDIT
 # SEE roll_webgl_conformance.py
-Current webgl revision 6517159d2016833417582d46a8dfc4e941ac0bd2
+Current webgl revision 87aaf44c5333e8b209baf5e11baa325b79ddeb62
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
index e02bc82..1f54f86 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
@@ -136,6 +136,8 @@
   std::unordered_map<std::string,
                      std::unique_ptr<BluetoothRemoteGattDescriptorMac>>
       gatt_descriptor_macs_;
+
+  base::WeakPtrFactory<BluetoothRemoteGattCharacteristicMac> weak_ptr_factory_;
 };
 
 // Stream operator for logging.
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
index 2f135e6..3c6e5a2 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
@@ -72,7 +72,8 @@
     : is_discovery_complete_(false),
       discovery_pending_count_(0),
       gatt_service_(gatt_service),
-      cb_characteristic_(cb_characteristic, base::scoped_policy::RETAIN) {
+      cb_characteristic_(cb_characteristic, base::scoped_policy::RETAIN),
+      weak_ptr_factory_(this) {
   uuid_ = BluetoothAdapterMac::BluetoothUUIDWithCBUUID(
       [cb_characteristic_.get() UUID]);
   identifier_ = base::SysNSStringToUTF8(
@@ -207,7 +208,7 @@
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::Bind(&BluetoothRemoteGattCharacteristicMac::DidWriteValue,
-                   base::Unretained(this), nil));
+                   weak_ptr_factory_.GetWeakPtr(), nil));
   }
 }
 
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
index 614f70e..587c3ebd 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
@@ -2413,6 +2413,31 @@
 }
 #endif  // defined(OS_ANDROID)
 
+#if defined(OS_MACOSX)
+// Tests that writing without response during a disconnect results in an error.
+// Only applies to macOS whose events arrive all on the UI thread. See other
+// *DuringDisconnect tests for Android and Windows whose events arrive on a
+// different thread.
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       WriteWithoutResponseDuringDisconnect) {
+  if (!PlatformSupportsLowEnergy()) {
+    LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
+    return;
+  }
+  ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate(
+      BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE));
+
+  characteristic1_->WriteRemoteCharacteristic(
+      std::vector<uint8_t>(), GetCallback(Call::NOT_EXPECTED),
+      GetGattErrorCallback(Call::EXPECTED));
+  SimulateGattDisconnection(device_);
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED,
+            last_gatt_error_code_);
+}
+#endif  // defined(OS_MACOSX)
+
 #if defined(OS_ANDROID)
 // Tests that start notifications requests after a device disconnects but before
 // the disconnect task runs result in an error.
diff --git a/device/gamepad/gamepad_monitor.cc b/device/gamepad/gamepad_monitor.cc
index 0f85717..7ba2e83 100644
--- a/device/gamepad/gamepad_monitor.cc
+++ b/device/gamepad/gamepad_monitor.cc
@@ -4,6 +4,8 @@
 
 #include "device/gamepad/gamepad_monitor.h"
 
+#include <utility>
+
 #include "base/memory/ptr_util.h"
 #include "base/memory/shared_memory.h"
 #include "device/gamepad/gamepad_service.h"
@@ -38,23 +40,21 @@
     gamepad_observer_->GamepadDisconnected(index, gamepad);
 }
 
-void GamepadMonitor::GamepadStartPolling(
-    const GamepadStartPollingCallback& callback) {
+void GamepadMonitor::GamepadStartPolling(GamepadStartPollingCallback callback) {
   DCHECK(!is_started_);
   is_started_ = true;
 
   GamepadService* service = GamepadService::GetInstance();
   service->ConsumerBecameActive(this);
-  callback.Run(service->GetSharedBufferHandle());
+  std::move(callback).Run(service->GetSharedBufferHandle());
 }
 
-void GamepadMonitor::GamepadStopPolling(
-    const GamepadStopPollingCallback& callback) {
+void GamepadMonitor::GamepadStopPolling(GamepadStopPollingCallback callback) {
   DCHECK(is_started_);
   is_started_ = false;
 
   GamepadService::GetInstance()->ConsumerBecameInactive(this);
-  callback.Run();
+  std::move(callback).Run();
 }
 
 void GamepadMonitor::SetObserver(mojom::GamepadObserverPtr gamepad_observer) {
diff --git a/device/gamepad/gamepad_monitor.h b/device/gamepad/gamepad_monitor.h
index fa4d374..19d0d0d 100644
--- a/device/gamepad/gamepad_monitor.h
+++ b/device/gamepad/gamepad_monitor.h
@@ -32,9 +32,8 @@
   void OnGamepadDisconnected(unsigned index, const Gamepad& gamepad) override;
 
   // mojom::GamepadMonitor implementation.
-  void GamepadStartPolling(
-      const GamepadStartPollingCallback& callback) override;
-  void GamepadStopPolling(const GamepadStopPollingCallback& callback) override;
+  void GamepadStartPolling(GamepadStartPollingCallback callback) override;
+  void GamepadStopPolling(GamepadStopPollingCallback callback) override;
   void SetObserver(mojom::GamepadObserverPtr gamepad_observer) override;
 
  private:
diff --git a/device/gamepad/public/interfaces/BUILD.gn b/device/gamepad/public/interfaces/BUILD.gn
index c1f78e4a..ffbf359 100644
--- a/device/gamepad/public/interfaces/BUILD.gn
+++ b/device/gamepad/public/interfaces/BUILD.gn
@@ -8,9 +8,6 @@
   sources = [
     "gamepad.mojom",
   ]
-
-  # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
-  use_once_callback = false
 }
 
 mojom("gamepad_struct_traits_test") {
@@ -21,7 +18,4 @@
   public_deps = [
     ":interfaces",
   ]
-
-  # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
-  use_once_callback = false
 }
diff --git a/device/gamepad/public/interfaces/gamepad_struct_traits_unittest.cc b/device/gamepad/public/interfaces/gamepad_struct_traits_unittest.cc
index f1c5c27c..5a731fe 100644
--- a/device/gamepad/public/interfaces/gamepad_struct_traits_unittest.cc
+++ b/device/gamepad/public/interfaces/gamepad_struct_traits_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "device/gamepad/public/cpp/gamepad.h"
@@ -175,9 +177,8 @@
  protected:
   GamepadStructTraitsTest() : binding_(this) {}
 
-  void PassGamepad(const Gamepad& send,
-                   const PassGamepadCallback& callback) override {
-    callback.Run(send);
+  void PassGamepad(const Gamepad& send, PassGamepadCallback callback) override {
+    std::move(callback).Run(send);
   }
 
   mojom::GamepadStructTraitsTestPtr GetGamepadStructTraitsTestProxy() {
diff --git a/extensions/common/permissions/permissions_data.cc b/extensions/common/permissions/permissions_data.cc
index 2002b29..245040e 100644
--- a/extensions/common/permissions/permissions_data.cc
+++ b/extensions/common/permissions/permissions_data.cc
@@ -158,10 +158,9 @@
 }
 
 const URLPatternSet& PermissionsData::PolicyBlockedHostsUnsafe() const {
-  DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+  runtime_lock_.AssertAcquired();
   if (uses_default_policy_host_restrictions)
     return default_policy_blocked_hosts();
-  runtime_lock_.AssertAcquired();
   return policy_blocked_hosts_unsafe_;
 }
 
@@ -171,10 +170,9 @@
 }
 
 const URLPatternSet& PermissionsData::PolicyAllowedHostsUnsafe() const {
-  DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+  runtime_lock_.AssertAcquired();
   if (uses_default_policy_host_restrictions)
     return default_policy_allowed_hosts();
-  runtime_lock_.AssertAcquired();
   return policy_allowed_hosts_unsafe_;
 }
 
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index 719ee2790..49124bc 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -200,6 +200,20 @@
   ]
 }
 
+if (headless_fontconfig_utils) {
+  static_library("headless_fontconfig_utils") {
+    sources = [
+      "public/util/fontconfig.cc",
+      "public/util/fontconfig.h",
+    ]
+
+    deps = [
+      "//third_party/fontconfig",
+      "//third_party/freetype",
+    ]
+  }
+}
+
 component("headless") {
   sources = [
     "app/headless_shell_switches.cc",
@@ -380,6 +394,10 @@
     deps += [ "//ui/ozone" ]
   }
 
+  if (headless_fontconfig_utils) {
+    deps += [ ":headless_fontconfig_utils" ]
+  }
+
   configs += [ ":headless_implementation" ]
 }
 
diff --git a/headless/headless.gni b/headless/headless.gni
index 0cc77229..ab0b2d10 100644
--- a/headless/headless.gni
+++ b/headless/headless.gni
@@ -5,4 +5,7 @@
 declare_args() {
   # Embed resource.pak file into the binary for easier distribution.
   headless_use_embedded_resources = false
+
+  # Provide bindings for font loading for headless embedders.
+  headless_fontconfig_utils = false
 }
diff --git a/headless/public/util/fontconfig.cc b/headless/public/util/fontconfig.cc
new file mode 100644
index 0000000..d859c19
--- /dev/null
+++ b/headless/public/util/fontconfig.cc
@@ -0,0 +1,119 @@
+// 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 "headless/public/util/fontconfig.h"
+
+// Should be included before freetype.h.
+#include <ft2build.h>
+
+#include <dirent.h>
+#include <fontconfig/fontconfig.h>
+#include <freetype/freetype.h>
+#include <set>
+#include <string>
+
+#include "base/logging.h"
+
+namespace headless {
+namespace {
+void GetFontFileNames(const FcFontSet* font_set,
+                      std::set<std::string>* file_names) {
+  if (font_set == NULL)
+    return;
+  for (int i = 0; i < font_set->nfont; ++i) {
+    FcPattern* pattern = font_set->fonts[i];
+    FcValue font_file;
+    if (FcPatternGet(pattern, "file", 0, &font_file) == FcResultMatch) {
+      file_names->insert(reinterpret_cast<const char*>(font_file.u.s));
+    } else {
+      VLOG(1) << "Failed to find filename.";
+      FcPatternPrint(pattern);
+    }
+  }
+}
+FcConfig* g_config = nullptr;
+}  // namespace
+
+void InitFonts(const char* fontconfig_path) {
+  // The following is roughly equivalent to calling FcInit().  We jump through
+  // a bunch of hoops here to avoid using fontconfig's directory scanning
+  // logic. The problem with fontconfig is that it follows symlinks when doing
+  // recursive directory scans.
+  //
+  // The approach below ignores any <dir>...</dir> entries in fonts.conf.  This
+  // is deliberate.  Specifying dirs is problematic because they're either
+  // absolute or relative to our process's current working directory which
+  // could be anything.  Instead we assume that all font files will be in the
+  // same directory as fonts.conf.  We'll scan + load them here.
+  FcConfig* config = FcConfigCreate();
+  g_config = config;
+  CHECK(config);
+
+  // FcConfigParseAndLoad is a seriously goofy function. Depending on whether
+  // name passed in begins with a slash, it will treat it either as a file name
+  // to be found in the directory where it expects to find the font
+  // configuration OR it will will treat it as a directory where it expects to
+  // find fonts.conf. The latter behavior is the one we want. Passing
+  // fontconfig_path via the environment is a quick and dirty way to get
+  // uniform behavior regardless whether it's a relative path or not.
+  setenv("FONTCONFIG_PATH", fontconfig_path, 1);
+  CHECK(FcConfigParseAndLoad(config, nullptr, FcTrue))
+      << "Failed to load font configuration.  FONTCONFIG_PATH="
+      << fontconfig_path;
+
+  DIR* fc_dir = opendir(fontconfig_path);
+  CHECK(fc_dir) << "Failed to open font directory " << fontconfig_path << ": "
+                << strerror(errno);
+
+  // The fonts must be loaded in a consistent order. This makes rendered results
+  // stable across runs, otherwise replacement font picks are random
+  // and cause flakiness.
+  std::set<std::string> fonts;
+  struct dirent entry, *result;
+  while (readdir_r(fc_dir, &entry, &result) == 0 && result != NULL) {
+    fonts.insert(result->d_name);
+  }
+  for (const std::string& font : fonts) {
+    const std::string full_path = fontconfig_path + ("/" + font);
+    struct stat statbuf;
+    CHECK_EQ(0, stat(full_path.c_str(), &statbuf))
+        << "Failed to stat " << full_path << ": " << strerror(errno);
+    if (S_ISREG(statbuf.st_mode)) {
+      // FcConfigAppFontAddFile will silently ignore non-fonts.
+      FcConfigAppFontAddFile(
+          config, reinterpret_cast<const FcChar8*>(full_path.c_str()));
+    }
+  }
+  closedir(fc_dir);
+  CHECK(FcConfigSetCurrent(config));
+
+  // Retrieve font from both of fontconfig's font sets for pre-loading.
+  std::set<std::string> font_files;
+  GetFontFileNames(FcConfigGetFonts(NULL, FcSetSystem), &font_files);
+  GetFontFileNames(FcConfigGetFonts(NULL, FcSetApplication), &font_files);
+  CHECK_GT(font_files.size(), 0u)
+      << "Font configuration doesn't contain any fonts!";
+
+  // Get freetype to load every font file we know about.  This will cause the
+  // font files to get cached in memory.  Once that's done we shouldn't have to
+  // access the file system for fonts at all.
+  FT_Library library;
+  FT_Init_FreeType(&library);
+  for (std::set<std::string>::const_iterator iter = font_files.begin();
+       iter != font_files.end(); ++iter) {
+    FT_Face face;
+    CHECK_EQ(0, FT_New_Face(library, iter->c_str(), 0, &face))
+        << "Failed to load font face: " << *iter;
+    FT_Done_Face(face);
+  }
+  FT_Done_FreeType(library);  // Cached stuff will stick around... ?
+}
+
+void ReleaseFonts() {
+  CHECK(g_config);
+  FcConfigDestroy(g_config);
+  FcFini();
+}
+
+}  // namespace headless
diff --git a/headless/public/util/fontconfig.h b/headless/public/util/fontconfig.h
new file mode 100644
index 0000000..2a504930
--- /dev/null
+++ b/headless/public/util/fontconfig.h
@@ -0,0 +1,20 @@
+// 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 HEADLESS_PUBLIC_UTIL_FONTCONFIG_H_
+#define HEADLESS_PUBLIC_UTIL_FONTCONFIG_H_
+
+namespace headless {
+
+// Initialize fontconfig by loading fonts from given path without following
+// symlinks. This is a wrapper around FcInit from libfreetype bundled with
+// Chromium modified to enable headless embedders to deploy in custom
+// environments.
+void InitFonts(const char* font_config_path);
+
+void ReleaseFonts();
+
+}  // namespace headless
+
+#endif  // HEADLESS_PUBLIC_UTIL_FONTCONFIG_H_
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index 6321c20..a45ae0a1 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -18,7 +18,7 @@
 #import "base/mac/bind_objc_block.h"
 #include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/macros.h"
 #include "base/path_service.h"
@@ -132,8 +132,8 @@
 #include "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
 #import "ios/public/provider/chrome/browser/user_feedback/user_feedback_provider.h"
 #import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
-#import "ios/third_party/material_roboto_font_loader_ios/src/src/MDCTypographyAdditions/MDFRobotoFontLoader+MDCTypographyAdditions.h"
 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h"
+#import "ios/third_party/material_roboto_font_loader_ios/src/src/MDCTypographyAdditions/MDFRobotoFontLoader+MDCTypographyAdditions.h"
 #include "ios/web/net/request_tracker_factory_impl.h"
 #include "ios/web/net/request_tracker_impl.h"
 #include "ios/web/net/web_http_protocol_handler_delegate.h"
@@ -331,6 +331,8 @@
   // appropriate pref changes.
   base::scoped_nsobject<MemoryDebuggerManager> _memoryDebuggerManager;
 
+  base::mac::ObjCPropertyReleaser _propertyReleaser_MainController;
+
   // Responsible for indexing chrome links (such as bookmarks, most likely...)
   // in system Spotlight index.
   base::scoped_nsobject<SpotlightManager> _spotlightManager;
@@ -547,6 +549,7 @@
 
 - (instancetype)init {
   if ((self = [super init])) {
+    _propertyReleaser_MainController.Init(self, [MainController class]);
     _startupTasks.reset([[StartupTasks alloc] init]);
   }
   return self;
@@ -557,7 +560,6 @@
   net::HTTPProtocolHandlerDelegate::SetInstance(nullptr);
   net::RequestTracker::SetRequestTrackerFactory(nullptr);
   [NSObject cancelPreviousPerformRequestsWithTarget:self];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/browser_state/BUILD.gn b/ios/chrome/browser/browser_state/BUILD.gn
index 6fcca06..359a3ad 100644
--- a/ios/chrome/browser/browser_state/BUILD.gn
+++ b/ios/chrome/browser/browser_state/BUILD.gn
@@ -106,6 +106,7 @@
     "//ios/net",
     "//ios/public/provider/chrome/browser",
     "//ios/public/provider/chrome/browser/signin",
+    "//ios/shared/chrome/browser/ui/browser_list",
     "//ios/web",
     "//net",
     "//net:extras",
diff --git a/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm b/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm
index a87f1b1..5faf7b4d1 100644
--- a/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm
+++ b/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm
@@ -46,6 +46,7 @@
 #include "ios/chrome/browser/translate/translate_accept_languages_factory.h"
 #include "ios/chrome/browser/undo/bookmark_undo_service_factory.h"
 #include "ios/chrome/browser/web_data_service_factory.h"
+#include "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -86,6 +87,7 @@
   ios::WebDataServiceFactory::GetInstance();
   ios::WebHistoryServiceFactory::GetInstance();
   AuthenticationServiceFactory::GetInstance();
+  BrowserListSessionServiceFactory::GetInstance();
   DesktopPromotionSyncServiceFactory::GetInstance();
   IOSChromeGCMProfileServiceFactory::GetInstance();
   IOSChromeLargeIconCacheFactory::GetInstance();
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm b/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm
index cbfc3772..0d0dfbf 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm
@@ -13,6 +13,7 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/sequenced_task_runner.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/sequenced_worker_pool.h"
 #include "components/cookie_config/cookie_store_util.h"
 #include "components/net_log/chrome_net_log.h"
@@ -86,11 +87,10 @@
 
   io_data_->InitializeMetricsEnabledStateOnUIThread();
 
-  base::SequencedWorkerPool* pool = web::WebThread::GetBlockingPool();
   scoped_refptr<base::SequencedTaskRunner> db_task_runner =
-      pool->GetSequencedTaskRunnerWithShutdownBehavior(
-          pool->GetSequenceToken(),
-          base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
+      base::CreateSequencedTaskRunnerWithTraits(
+          {base::MayBlock(), base::TaskPriority::BACKGROUND,
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
 }
 
 scoped_refptr<IOSChromeURLRequestContextGetter>
@@ -260,8 +260,8 @@
     scoped_refptr<net::SQLiteChannelIDStore> channel_id_db =
         new net::SQLiteChannelIDStore(
             lazy_params_->channel_id_path,
-            web::WebThread::GetBlockingPool()->GetSequencedTaskRunner(
-                web::WebThread::GetBlockingPool()->GetSequenceToken()));
+            base::CreateSequencedTaskRunnerWithTraits(
+                {base::MayBlock(), base::TaskPriority::BACKGROUND}));
     channel_id_service = new net::ChannelIDService(
         new net::DefaultChannelIDStore(channel_id_db.get()));
   }
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_io_data.cc b/ios/chrome/browser/browser_state/chrome_browser_state_io_data.cc
index c7efc06..217d1b6 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_io_data.cc
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_io_data.cc
@@ -22,7 +22,7 @@
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
-#include "base/threading/sequenced_worker_pool.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/about_handler/about_protocol_handler.h"
 #include "components/content_settings/core/browser/content_settings_provider.h"
@@ -356,11 +356,11 @@
       std::move(profile_params_->proxy_config_service),
       true /* quick_check_enabled */);
   transport_security_state_.reset(new net::TransportSecurityState());
-  base::SequencedWorkerPool* pool = web::WebThread::GetBlockingPool();
   transport_security_persister_.reset(new net::TransportSecurityPersister(
       transport_security_state_.get(), profile_params_->path,
-      pool->GetSequencedTaskRunnerWithShutdownBehavior(
-          pool->GetSequenceToken(), base::SequencedWorkerPool::BLOCK_SHUTDOWN),
+      base::CreateSequencedTaskRunnerWithTraits(
+          {base::MayBlock(), base::TaskPriority::BACKGROUND,
+           base::TaskShutdownBehavior::BLOCK_SHUTDOWN}),
       IsOffTheRecord()));
 
   certificate_report_sender_ =
@@ -400,8 +400,9 @@
   bool set_protocol = job_factory->SetProtocolHandler(
       url::kFileScheme,
       base::MakeUnique<net::FileProtocolHandler>(
-          web::WebThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior(
-              base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)));
+          base::CreateTaskRunnerWithTraits(
+              {base::MayBlock(), base::TaskPriority::BACKGROUND,
+               base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})));
   DCHECK(set_protocol);
 
   set_protocol = job_factory->SetProtocolHandler(
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_removal_controller.mm b/ios/chrome/browser/browser_state/chrome_browser_state_removal_controller.mm
index ba86684..294b87dc 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_removal_controller.mm
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_removal_controller.mm
@@ -12,6 +12,7 @@
 #include "base/location.h"
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
+#include "base/task_scheduler/post_task.h"
 #include "components/prefs/pref_service.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "ios/chrome/browser/application_context.h"
@@ -23,7 +24,6 @@
 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
 #import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
 #include "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
-#include "ios/web/public/web_thread.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -143,8 +143,9 @@
 
   if (is_removing_browser_states) {
     SetHasBrowserStateBeenRemoved(true);
-    web::WebThread::PostBlockingPoolTask(
-        FROM_HERE, base::Bind(&NukeBrowserStates, browser_states_to_nuke));
+    base::PostTaskWithTraits(
+        FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
+        base::BindOnce(&NukeBrowserStates, browser_states_to_nuke));
   }
 }
 
diff --git a/ios/chrome/browser/content_suggestions/content_suggestions_mediator.mm b/ios/chrome/browser/content_suggestions/content_suggestions_mediator.mm
index cbd7a4e0..9adb45bc 100644
--- a/ios/chrome/browser/content_suggestions/content_suggestions_mediator.mm
+++ b/ios/chrome/browser/content_suggestions/content_suggestions_mediator.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/content_suggestions/content_suggestions_mediator.h"
 
 #include "base/mac/bind_objc_block.h"
+#include "base/mac/foundation_util.h"
 #include "base/memory/ptr_util.h"
 #include "base/optional.h"
 #include "components/favicon/core/large_icon_service.h"
@@ -17,15 +18,15 @@
 #import "ios/chrome/browser/content_suggestions/content_suggestions_service_bridge_observer.h"
 #import "ios/chrome/browser/content_suggestions/mediator_util.h"
 #include "ios/chrome/browser/ntp_tiles/most_visited_sites_observer_bridge.h"
-#import "ios/chrome/browser/ui/content_suggestions/content_suggestion.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_commands.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_data_sink.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_image_fetcher.h"
 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestions_section_information.h"
 #import "ios/chrome/browser/ui/favicon/favicon_attributes_provider.h"
-#import "ios/chrome/browser/ui/reading_list/reading_list_collection_view_item.h"
-#import "ios/chrome/browser/ui/reading_list/reading_list_utils.h"
 #include "ui/gfx/image/image.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -34,8 +35,10 @@
 
 namespace {
 
+using CSCollectionViewItem = CollectionViewItem<SuggestedContent>;
+
 // Size of the favicon returned by the provider.
-const CGFloat kDefaultFaviconSize = 16;
+const CGFloat kDefaultFaviconSize = 48;
 // Maximum number of most visited tiles fetched.
 const NSInteger kMaxNumMostVisitedTiles = 8;
 
@@ -70,21 +73,6 @@
 @property(nonatomic, nullable, strong)
     FaviconAttributesProvider* attributesProvider;
 
-// Converts the |suggestions| from |category| to ContentSuggestion and adds them
-// to the |contentArray|  if the category is available.
-- (void)addSuggestions:
-            (const std::vector<ntp_snippets::ContentSuggestion>&)suggestions
-          fromCategory:(ntp_snippets::Category&)category
-               toArray:(NSMutableArray<ContentSuggestion*>*)contentArray;
-
-// Adds the section information for |category| in
-// self.sectionInformationByCategory.
-- (void)addSectionInformationForCategory:(ntp_snippets::Category)category;
-
-// Returns a CategoryWrapper acting as a key for this section info.
-- (ContentSuggestionsCategoryWrapper*)categoryWrapperForSectionInfo:
-    (ContentSuggestionsSectionInformation*)sectionInfo;
-
 @end
 
 @implementation ContentSuggestionsMediator
@@ -138,22 +126,39 @@
 
 #pragma mark - ContentSuggestionsDataSource
 
-- (NSArray<ContentSuggestion*>*)allSuggestions {
-  NSMutableArray<ContentSuggestion*>* dataHolders = [NSMutableArray array];
+- (NSArray<ContentSuggestionsSectionInformation*>*)sectionsInfo {
+  NSMutableArray<ContentSuggestionsSectionInformation*>* sectionsInfo =
+      [NSMutableArray array];
 
-  [self addMostVisitedToArray:dataHolders];
+  if (!self.mostVisitedData.empty()) {
+    [sectionsInfo addObject:self.mostVisitedSectionInfo];
+  }
 
-  [self addContentSuggestionsToArray:dataHolders];
+  std::vector<ntp_snippets::Category> categories =
+      self.contentService->GetCategories();
 
-  return dataHolders;
+  for (auto& category : categories) {
+    ContentSuggestionsCategoryWrapper* categoryWrapper =
+        [ContentSuggestionsCategoryWrapper wrapperWithCategory:category];
+    if (!self.sectionInformationByCategory[categoryWrapper]) {
+      [self addSectionInformationForCategory:category];
+    }
+    [sectionsInfo addObject:self.sectionInformationByCategory[categoryWrapper]];
+  }
+
+  return sectionsInfo;
 }
 
-- (NSArray<ContentSuggestion*>*)suggestionsForSection:
+- (NSArray<CSCollectionViewItem*>*)itemsForSectionInfo:
     (ContentSuggestionsSectionInformation*)sectionInfo {
-  NSMutableArray* convertedSuggestions = [NSMutableArray array];
+  NSMutableArray<CSCollectionViewItem*>* convertedSuggestions =
+      [NSMutableArray array];
 
   if (sectionInfo == self.mostVisitedSectionInfo) {
-    [self addMostVisitedToArray:convertedSuggestions];
+    for (const ntp_tiles::NTPTile& tile : self.mostVisitedData) {
+      [convertedSuggestions
+          addObject:ConvertNTPTile(tile, self.mostVisitedSectionInfo)];
+    }
   } else {
     ntp_snippets::Category category =
         [[self categoryWrapperForSectionInfo:sectionInfo] category];
@@ -162,7 +167,7 @@
         self.contentService->GetSuggestionsForCategory(category);
     [self addSuggestions:suggestions
             fromCategory:category
-                 toArray:convertedSuggestions];
+             toItemArray:convertedSuggestions];
   }
 
   return convertedSuggestions;
@@ -177,14 +182,9 @@
                     fromSectionInfo:
                         (ContentSuggestionsSectionInformation*)sectionInfo
                            callback:(MoreSuggestionsFetched)callback {
-  if (![self isRelatedToContentSuggestionsService:sectionInfo])
+  if (![self isRelatedToContentSuggestionsService:sectionInfo]) {
+    callback(nil);
     return;
-
-  std::set<std::string> known_suggestion_ids;
-  for (ContentSuggestionIdentifier* identifier in knownSuggestions) {
-    if (identifier.sectionInfo != sectionInfo)
-      continue;
-    known_suggestion_ids.insert(identifier.IDInSection);
   }
 
   ContentSuggestionsCategoryWrapper* wrapper =
@@ -194,13 +194,17 @@
       self.contentService->GetCategoryInfo([wrapper category]);
 
   if (!categoryInfo) {
+    callback(nil);
     return;
   }
+
   switch (categoryInfo->additional_action()) {
     case ntp_snippets::ContentSuggestionsAdditionalAction::NONE:
+      callback(nil);
       return;
 
     case ntp_snippets::ContentSuggestionsAdditionalAction::VIEW_ALL:
+      callback(nil);
       if ([wrapper category].IsKnownCategory(
               ntp_snippets::KnownCategories::READING_LIST)) {
         [self.commandHandler openReadingList];
@@ -208,6 +212,13 @@
       break;
 
     case ntp_snippets::ContentSuggestionsAdditionalAction::FETCH: {
+      std::set<std::string> known_suggestion_ids;
+      for (ContentSuggestionIdentifier* identifier in knownSuggestions) {
+        if (identifier.sectionInfo != sectionInfo)
+          continue;
+        known_suggestion_ids.insert(identifier.IDInSection);
+      }
+
       __weak ContentSuggestionsMediator* weakSelf = self;
       ntp_snippets::FetchDoneCallback serviceCallback = base::Bind(
           &BindWrapper,
@@ -227,17 +238,40 @@
   }
 }
 
-- (void)fetchFaviconAttributesForURL:(const GURL&)URL
-                          completion:(void (^)(FaviconAttributes*))completion {
-  [self.attributesProvider fetchFaviconAttributesForURL:URL
+- (void)fetchFaviconAttributesForItem:(CSCollectionViewItem*)item
+                           completion:(void (^)(FaviconAttributes*))completion {
+  ContentSuggestionsSectionInformation* sectionInfo =
+      item.suggestionIdentifier.sectionInfo;
+  GURL url;
+  if (![self isRelatedToContentSuggestionsService:sectionInfo]) {
+    ContentSuggestionsMostVisitedItem* mostVisited =
+        base::mac::ObjCCast<ContentSuggestionsMostVisitedItem>(item);
+    url = mostVisited.URL;
+  } else {
+    ContentSuggestionsItem* suggestionItem =
+        base::mac::ObjCCast<ContentSuggestionsItem>(item);
+    url = suggestionItem.URL;
+  }
+  [self.attributesProvider fetchFaviconAttributesForURL:url
                                              completion:completion];
 }
 
-- (void)fetchFaviconImageForSuggestion:(ContentSuggestionIdentifier*)suggestion
-                            completion:(void (^)(UIImage*))completion {
-  if (!completion)
+- (void)fetchFaviconImageForItem:(CSCollectionViewItem*)item
+                      completion:(void (^)(UIImage*))completion {
+  ContentSuggestionsSectionInformation* sectionInfo =
+      item.suggestionIdentifier.sectionInfo;
+  if (![self isRelatedToContentSuggestionsService:sectionInfo]) {
     return;
-
+  }
+  ContentSuggestionsItem* suggestionItem =
+      base::mac::ObjCCast<ContentSuggestionsItem>(item);
+  ntp_snippets::Category category =
+      [[self categoryWrapperForSectionInfo:sectionInfo] category];
+  if (!category.IsKnownCategory(ntp_snippets::KnownCategories::ARTICLES)) {
+    // TODO(crbug.com/721266): remove this guard once the choice to download the
+    // favicon from the google server is done in the provider.
+    return;
+  }
   void (^imageCallback)(const gfx::Image&) = ^(const gfx::Image& image) {
     if (!image.IsEmpty()) {
       completion([image.ToUIImage() copy]);
@@ -246,9 +280,7 @@
 
   ntp_snippets::ContentSuggestion::ID identifier =
       ntp_snippets::ContentSuggestion::ID(
-          [[self categoryWrapperForSectionInfo:suggestion.sectionInfo]
-              category],
-          suggestion.IDInSection);
+          category, suggestionItem.suggestionIdentifier.IDInSection);
   self.contentService->FetchSuggestionFavicon(
       identifier, /* minimum_size_in_pixel = */ 1, kDefaultFaviconSize,
       base::BindBlockArc(imageCallback));
@@ -346,10 +378,12 @@
 
 #pragma mark - Private
 
+// Converts the |suggestions| from |category| to CSCollectionViewItem and adds
+// them to the |contentArray| if the category is available.
 - (void)addSuggestions:
             (const std::vector<ntp_snippets::ContentSuggestion>&)suggestions
           fromCategory:(ntp_snippets::Category&)category
-               toArray:(NSMutableArray<ContentSuggestion*>*)contentArray {
+           toItemArray:(NSMutableArray<CSCollectionViewItem*>*)itemArray {
   if (!ntp_snippets::IsCategoryStatusAvailable(
           self.contentService->GetCategoryStatus(category))) {
     return;
@@ -360,32 +394,18 @@
   if (!self.sectionInformationByCategory[categoryWrapper]) {
     [self addSectionInformationForCategory:category];
   }
-
+  ContentSuggestionsSectionInformation* sectionInfo =
+      self.sectionInformationByCategory[categoryWrapper];
   for (auto& contentSuggestion : suggestions) {
-    ContentSuggestion* suggestion = ConvertContentSuggestion(contentSuggestion);
+    CSCollectionViewItem* suggestion =
+        ConvertSuggestion(contentSuggestion, sectionInfo, category);
 
-    suggestion.type = TypeForCategory(category);
-
-    suggestion.suggestionIdentifier.sectionInfo =
-        self.sectionInformationByCategory[categoryWrapper];
-
-    if (category.IsKnownCategory(ntp_snippets::KnownCategories::READING_LIST)) {
-      suggestion.availableOffline =
-          contentSuggestion.reading_list_suggestion_extra()->distilled;
-    }
-
-    [contentArray addObject:suggestion];
-  }
-
-  if (suggestions.size() == 0) {
-    ContentSuggestion* suggestion = EmptySuggestion();
-    suggestion.suggestionIdentifier.sectionInfo =
-        self.sectionInformationByCategory[categoryWrapper];
-
-    [contentArray addObject:suggestion];
+    [itemArray addObject:suggestion];
   }
 }
 
+// Adds the section information for |category| in
+// self.sectionInformationByCategory.
 - (void)addSectionInformationForCategory:(ntp_snippets::Category)category {
   base::Optional<ntp_snippets::CategoryInfo> categoryInfo =
       self.contentService->GetCategoryInfo(category);
@@ -397,6 +417,7 @@
       wrapperWithCategory:category]] = sectionInfo;
 }
 
+// Returns a CategoryWrapper acting as a key for this section info.
 - (ContentSuggestionsCategoryWrapper*)categoryWrapperForSectionInfo:
     (ContentSuggestionsSectionInformation*)sectionInfo {
   return [[self.sectionInformationByCategory allKeysForObject:sectionInfo]
@@ -409,45 +430,15 @@
             (const std::vector<ntp_snippets::ContentSuggestion>&)suggestions
                  withStatusCode:(ntp_snippets::Status)statusCode
                        callback:(MoreSuggestionsFetched)callback {
+  NSMutableArray<CSCollectionViewItem*>* contentSuggestions = nil;
   if (statusCode.IsSuccess() && !suggestions.empty() && callback) {
-    NSMutableArray<ContentSuggestion*>* contentSuggestions =
-        [NSMutableArray array];
+    contentSuggestions = [NSMutableArray array];
     ntp_snippets::Category category = suggestions[0].id().category();
     [self addSuggestions:suggestions
             fromCategory:category
-                 toArray:contentSuggestions];
-    callback(contentSuggestions);
+             toItemArray:contentSuggestions];
   }
-}
-
-// Adds all the suggestions from the |contentService| to |suggestions|.
-- (void)addContentSuggestionsToArray:
-    (NSMutableArray<ContentSuggestion*>*)arrayToFill {
-  std::vector<ntp_snippets::Category> categories =
-      self.contentService->GetCategories();
-
-  for (auto& category : categories) {
-    const std::vector<ntp_snippets::ContentSuggestion>& suggestions =
-        self.contentService->GetSuggestionsForCategory(category);
-    [self addSuggestions:suggestions fromCategory:category toArray:arrayToFill];
-  }
-}
-
-// Adds all the suggestions for the |mostVisitedData| to |suggestions|.
-- (void)addMostVisitedToArray:(NSMutableArray<ContentSuggestion*>*)arrayToFill {
-  if (self.mostVisitedData.empty()) {
-    ContentSuggestion* suggestion = EmptySuggestion();
-    suggestion.suggestionIdentifier.sectionInfo = self.mostVisitedSectionInfo;
-    [arrayToFill addObject:suggestion];
-
-    return;
-  }
-
-  for (const ntp_tiles::NTPTile& tile : self.mostVisitedData) {
-    ContentSuggestion* suggestion = ConvertNTPTile(tile);
-    suggestion.suggestionIdentifier.sectionInfo = self.mostVisitedSectionInfo;
-    [arrayToFill addObject:suggestion];
-  }
+  callback(contentSuggestions);
 }
 
 // Returns whether the |sectionInfo| is associated with a category from the
diff --git a/ios/chrome/browser/content_suggestions/mediator_util.h b/ios/chrome/browser/content_suggestions/mediator_util.h
index 1651ca97..9e06bfa 100644
--- a/ios/chrome/browser/content_suggestions/mediator_util.h
+++ b/ios/chrome/browser/content_suggestions/mediator_util.h
@@ -11,15 +11,14 @@
 #include "components/ntp_snippets/content_suggestion.h"
 #include "components/ntp_snippets/status.h"
 #include "components/ntp_tiles/ntp_tile.h"
-#import "ios/chrome/browser/ui/content_suggestions/content_suggestion.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h"
 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestions_section_information.h"
 
 namespace ntp_snippets {
-class ContentSuggestion;
 class Category;
-class CategoryInfo;
 }
 
+@class CollectionViewItem;
 @class ContentSuggestionsCategoryWrapper;
 
 // TODO(crbug.com/701275): Once base::BindBlock supports the move semantics,
@@ -32,21 +31,15 @@
     ntp_snippets::Status status_code,
     std::vector<ntp_snippets::ContentSuggestion> suggestions);
 
-// Returns the Type for this |category|.
-ContentSuggestionType TypeForCategory(ntp_snippets::Category category);
-
 // Returns the section ID for this |category|.
 ContentSuggestionsSectionID SectionIDForCategory(
     ntp_snippets::Category category);
 
-// Returns the section layout corresponding to the category |layout|.
-ContentSuggestionsSectionLayout SectionLayoutForLayout(
-    ntp_snippets::ContentSuggestionsCardLayout layout);
-
-// Converts a ntp_snippets::ContentSuggestion to an Objective-C
-// ContentSuggestion.
-ContentSuggestion* ConvertContentSuggestion(
-    const ntp_snippets::ContentSuggestion& contentSuggestion);
+// Converts a ntp_snippets::ContentSuggestion to a CollectionViewItem.
+CollectionViewItem<SuggestedContent>* ConvertSuggestion(
+    const ntp_snippets::ContentSuggestion& contentSuggestion,
+    ContentSuggestionsSectionInformation* sectionInfo,
+    ntp_snippets::Category category);
 
 // Returns a SectionInformation for a |category|, filled with the
 // |categoryInfo|.
@@ -60,17 +53,16 @@
     ContentSuggestionsCategoryWrapper* category,
     const std::string& id_in_category);
 
-// Creates and returns an empty suggestion.
-ContentSuggestion* EmptySuggestion();
-
 // Creates and returns a SectionInfo for the Most Visited section.
 ContentSuggestionsSectionInformation* MostVisitedSectionInformation();
 
 // Records the page impression of the ntp tiles.
 void RecordPageImpression(const std::vector<ntp_tiles::NTPTile>& mostVisited);
 
-// Converts a ntp_snippets::ContentSuggestion to an Objective-C
-// ContentSuggestion.
-ContentSuggestion* ConvertNTPTile(const ntp_tiles::NTPTile& tile);
+// Converts a ntp_snippets::ContentSuggestion to an adapted CollectionViewItem
+// with a |sectionInfo|.
+CollectionViewItem<SuggestedContent>* ConvertNTPTile(
+    const ntp_tiles::NTPTile& tile,
+    ContentSuggestionsSectionInformation* sectionInfo);
 
 #endif  // IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_MEDIATOR_UTIL_H_
diff --git a/ios/chrome/browser/content_suggestions/mediator_util.mm b/ios/chrome/browser/content_suggestions/mediator_util.mm
index d4ea2f9d..5b428ba 100644
--- a/ios/chrome/browser/content_suggestions/mediator_util.mm
+++ b/ios/chrome/browser/content_suggestions/mediator_util.mm
@@ -10,6 +10,10 @@
 #include "components/rappor/rappor_service_impl.h"
 #include "ios/chrome/browser/application_context.h"
 #import "ios/chrome/browser/content_suggestions/content_suggestions_category_wrapper.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h"
 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ui/base/l10n/l10n_util_mac.h"
@@ -29,15 +33,6 @@
   }
 }
 
-ContentSuggestionType TypeForCategory(ntp_snippets::Category category) {
-  if (category.IsKnownCategory(ntp_snippets::KnownCategories::ARTICLES))
-    return ContentSuggestionTypeArticle;
-  if (category.IsKnownCategory(ntp_snippets::KnownCategories::READING_LIST))
-    return ContentSuggestionTypeReadingList;
-
-  return ContentSuggestionTypeEmpty;
-}
-
 ContentSuggestionsSectionID SectionIDForCategory(
     ntp_snippets::Category category) {
   if (category.IsKnownCategory(ntp_snippets::KnownCategories::ARTICLES))
@@ -48,19 +43,15 @@
   return ContentSuggestionsSectionUnknown;
 }
 
-ContentSuggestionsSectionLayout SectionLayoutForLayout(
-    ntp_snippets::ContentSuggestionsCardLayout layout) {
-  // For now, only cards are relevant.
-  return ContentSuggestionsSectionLayoutCard;
-}
-
-ContentSuggestion* ConvertContentSuggestion(
-    const ntp_snippets::ContentSuggestion& contentSuggestion) {
-  ContentSuggestion* suggestion = [[ContentSuggestion alloc] init];
-
-  suggestion.title = base::SysUTF16ToNSString(contentSuggestion.title());
-  suggestion.text = base::SysUTF16ToNSString(contentSuggestion.snippet_text());
-  suggestion.url = contentSuggestion.url();
+CollectionViewItem<SuggestedContent>* ConvertSuggestion(
+    const ntp_snippets::ContentSuggestion& contentSuggestion,
+    ContentSuggestionsSectionInformation* sectionInfo,
+    ntp_snippets::Category category) {
+  ContentSuggestionsItem* suggestion = [[ContentSuggestionsItem alloc]
+      initWithType:0
+             title:base::SysUTF16ToNSString(contentSuggestion.title())
+          subtitle:base::SysUTF16ToNSString(contentSuggestion.snippet_text())
+               url:contentSuggestion.url()];
 
   suggestion.publisher =
       base::SysUTF16ToNSString(contentSuggestion.publisher_name());
@@ -69,6 +60,15 @@
   suggestion.suggestionIdentifier = [[ContentSuggestionIdentifier alloc] init];
   suggestion.suggestionIdentifier.IDInSection =
       contentSuggestion.id().id_within_category();
+  suggestion.suggestionIdentifier.sectionInfo = sectionInfo;
+
+  if (category.IsKnownCategory(ntp_snippets::KnownCategories::READING_LIST)) {
+    suggestion.availableOffline =
+        contentSuggestion.reading_list_suggestion_extra()->distilled;
+  }
+  if (category.IsKnownCategory(ntp_snippets::KnownCategories::ARTICLES)) {
+    suggestion.hasImage = YES;
+  }
 
   return suggestion;
 }
@@ -80,7 +80,7 @@
       [[ContentSuggestionsSectionInformation alloc]
           initWithSectionID:SectionIDForCategory(category)];
   if (categoryInfo) {
-    sectionInfo.layout = SectionLayoutForLayout(categoryInfo->card_layout());
+    sectionInfo.layout = ContentSuggestionsSectionLayoutCard;
     sectionInfo.showIfEmpty = categoryInfo->show_if_empty();
     sectionInfo.emptyText =
         base::SysUTF16ToNSString(categoryInfo->no_suggestions_message());
@@ -100,14 +100,6 @@
   return ntp_snippets::ContentSuggestion::ID(category.category, id_in_category);
 }
 
-ContentSuggestion* EmptySuggestion() {
-  ContentSuggestion* suggestion = [[ContentSuggestion alloc] init];
-  suggestion.type = ContentSuggestionTypeEmpty;
-  suggestion.suggestionIdentifier = [[ContentSuggestionIdentifier alloc] init];
-
-  return suggestion;
-}
-
 ContentSuggestionsSectionInformation* MostVisitedSectionInformation() {
   ContentSuggestionsSectionInformation* sectionInfo =
       [[ContentSuggestionsSectionInformation alloc]
@@ -130,15 +122,18 @@
       tiles, GetApplicationContext()->GetRapporServiceImpl());
 }
 
-ContentSuggestion* ConvertNTPTile(const ntp_tiles::NTPTile& tile) {
-  ContentSuggestion* suggestion = [[ContentSuggestion alloc] init];
+CollectionViewItem<SuggestedContent>* ConvertNTPTile(
+    const ntp_tiles::NTPTile& tile,
+    ContentSuggestionsSectionInformation* sectionInfo) {
+  ContentSuggestionsMostVisitedItem* suggestion =
+      [[ContentSuggestionsMostVisitedItem alloc] initWithType:0];
 
   suggestion.title = base::SysUTF16ToNSString(tile.title);
-  suggestion.url = tile.url;
-  suggestion.type = ContentSuggestionTypeMostVisited;
+  suggestion.URL = tile.url;
 
   suggestion.suggestionIdentifier = [[ContentSuggestionIdentifier alloc] init];
   suggestion.suggestionIdentifier.IDInSection = tile.url.spec();
+  suggestion.suggestionIdentifier.sectionInfo = sectionInfo;
 
   return suggestion;
 }
diff --git a/ios/chrome/browser/passwords/update_password_infobar_controller.mm b/ios/chrome/browser/passwords/update_password_infobar_controller.mm
index 22d6a445..656b3bb 100644
--- a/ios/chrome/browser/passwords/update_password_infobar_controller.mm
+++ b/ios/chrome/browser/passwords/update_password_infobar_controller.mm
@@ -4,7 +4,7 @@
 
 #import "ios/chrome/browser/passwords/update_password_infobar_controller.h"
 
-#include "base/mac/objc_release_properties.h"
+#import "base/mac/objc_property_releaser.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "ios/chrome/browser/infobars/confirm_infobar_controller+protected.h"
@@ -19,6 +19,8 @@
 }
 
 @interface UpdatePasswordInfoBarController ()<SelectorCoordinatorDelegate> {
+  base::mac::ObjCPropertyReleaser
+      _propertyReleaser_UpdatePasswordInfoBarController;
   IOSChromeUpdatePasswordInfoBarDelegate* _delegate;
 }
 @property(nonatomic, retain) SelectorCoordinator* selectorCoordinator;
@@ -28,9 +30,13 @@
 
 @synthesize selectorCoordinator = _selectorCoordinator;
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
+- (instancetype)initWithDelegate:(InfoBarViewDelegate*)delegate {
+  self = [super initWithDelegate:delegate];
+  if (self) {
+    _propertyReleaser_UpdatePasswordInfoBarController.Init(
+        self, [UpdatePasswordInfoBarController class]);
+  }
+  return self;
 }
 
 - (InfoBarView*)viewForDelegate:
diff --git a/ios/chrome/browser/snapshots/snapshot_cache.mm b/ios/chrome/browser/snapshots/snapshot_cache.mm
index a2174949..7fb720d 100644
--- a/ios/chrome/browser/snapshots/snapshot_cache.mm
+++ b/ios/chrome/browser/snapshots/snapshot_cache.mm
@@ -13,7 +13,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/mac/bind_objc_block.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
@@ -157,6 +157,8 @@
   // be requested to be saved to disk when the application is backgrounded.
   base::scoped_nsobject<NSString> backgroundingImageSessionId_;
   base::scoped_nsobject<UIImage> backgroundingColorImage_;
+
+  base::mac::ObjCPropertyReleaser propertyReleaser_SnapshotCache_;
 }
 
 @synthesize pinnedIDs = pinnedIDs_;
@@ -169,6 +171,8 @@
 - (id)init {
   if ((self = [super init])) {
     DCHECK_CURRENTLY_ON(web::WebThread::UI);
+    propertyReleaser_SnapshotCache_.Init(self, [SnapshotCache class]);
+
     if ([self usesLRUCache]) {
       lruCache_.reset(
           [[LRUCache alloc] initWithCacheSize:kLRUCacheMaxCapacity]);
@@ -208,7 +212,6 @@
       removeObserver:self
                 name:UIApplicationDidBecomeActiveNotification
               object:nil];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/ui/authentication/authentication_flow.mm b/ios/chrome/browser/ui/authentication/authentication_flow.mm
index f00b3dc..ca6c1dd 100644
--- a/ios/chrome/browser/ui/authentication/authentication_flow.mm
+++ b/ios/chrome/browser/ui/authentication/authentication_flow.mm
@@ -6,7 +6,7 @@
 
 #include "base/ios/weak_nsobject.h"
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_block.h"
 #include "base/mac/scoped_nsobject.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
@@ -101,6 +101,8 @@
   // is in progress to ensure it outlives any attempt to destroy it in
   // |_signInCompletion|.
   base::scoped_nsobject<AuthenticationFlow> _selfRetainer;
+
+  base::mac::ObjCPropertyReleaser _propertyReleaser_AuthenticationFlow;
 }
 
 @synthesize handlingError = _handlingError;
@@ -122,15 +124,11 @@
     _postSignInAction = postSignInAction;
     _presentingViewController.reset([presentingViewController retain]);
     _state = BEGIN;
+    _propertyReleaser_AuthenticationFlow.Init(self, [AuthenticationFlow class]);
   }
   return self;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (void)startSignInWithCompletion:(CompletionCallback)completion {
   DCHECK_EQ(BEGIN, _state);
   DCHECK(!_signInCompletion);
diff --git a/ios/chrome/browser/ui/content_suggestions/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
index 7cacefbe..c4d50084 100644
--- a/ios/chrome/browser/ui/content_suggestions/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
@@ -4,8 +4,6 @@
 
 source_set("content_suggestions") {
   sources = [
-    "content_suggestion.h",
-    "content_suggestion.mm",
     "content_suggestions_collection_updater.h",
     "content_suggestions_collection_updater.mm",
     "content_suggestions_collection_utils.h",
@@ -50,6 +48,7 @@
     "//base",
     "//ios/chrome/browser/ui",
     "//ios/chrome/browser/ui/collection_view",
+    "//ios/chrome/browser/ui/content_suggestions/cells",
     "//ios/chrome/browser/ui/content_suggestions/identifier",
     "//ios/chrome/test/base",
     "//testing/gtest",
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/cells/BUILD.gn
index a42fb2a..2b23598 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/cells/BUILD.gn
@@ -12,6 +12,7 @@
     "content_suggestions_most_visited_item.mm",
     "content_suggestions_text_item.h",
     "content_suggestions_text_item.mm",
+    "suggested_content.h",
   ]
   deps = [
     "//base",
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.h b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.h
index a27b4823..72a12b1 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.h
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.h
@@ -6,7 +6,7 @@
 #define IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CELLS_CONTENT_SUGGESTIONS_ITEM_H_
 
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
-#import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h"
 #import "ios/third_party/material_components_ios/src/components/CollectionCells/src/MaterialCollectionCells.h"
 
 namespace base {
@@ -18,36 +18,22 @@
 @class FaviconViewNew;
 class GURL;
 
-// Delegate for a ContentSuggestionsItem.
-@protocol ContentSuggestionsItemDelegate
-
-// Loads the image associated with this item.
-- (void)loadImageForSuggestionItem:(ContentSuggestionsItem*)suggestionItem;
-
-@end
-
 // Item for an article in the suggestions.
-@interface ContentSuggestionsItem
-    : CollectionViewItem<ContentSuggestionIdentification>
+@interface ContentSuggestionsItem : CollectionViewItem<SuggestedContent>
 
 // Initialize an article with a |title|, a |subtitle|, an |image| and the |url|
 // to the full article. |type| is the type of the item.
 - (instancetype)initWithType:(NSInteger)type
                        title:(NSString*)title
                     subtitle:(NSString*)subtitle
-                    delegate:(id<ContentSuggestionsItemDelegate>)delegate
                          url:(const GURL&)url NS_DESIGNATED_INITIALIZER;
 
 - (instancetype)initWithType:(NSInteger)type NS_UNAVAILABLE;
 
 @property(nonatomic, copy, readonly) NSString* title;
-@property(nonatomic, strong) UIImage* image;
 @property(nonatomic, readonly, assign) GURL URL;
 @property(nonatomic, copy) NSString* publisher;
 @property(nonatomic, assign) base::Time publishDate;
-@property(nonatomic, weak) id<ContentSuggestionsItemDelegate> delegate;
-// Attributes for favicon.
-@property(nonatomic, strong) FaviconAttributes* attributes;
 // Whether the suggestion has an image associated.
 @property(nonatomic, assign) BOOL hasImage;
 // Whether the suggestion is available offline. If YES, an icon is displayed.
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.mm b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.mm
index ffa7778..985d582 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.mm
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.mm
@@ -6,6 +6,7 @@
 
 #include "base/time/time.h"
 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
+#import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
 #import "ios/chrome/browser/ui/favicon/favicon_attributes.h"
 #import "ios/chrome/browser/ui/favicon/favicon_view.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
@@ -74,7 +75,6 @@
 - (instancetype)initWithType:(NSInteger)type
                        title:(NSString*)title
                     subtitle:(NSString*)subtitle
-                    delegate:(id<ContentSuggestionsItemDelegate>)delegate
                          url:(const GURL&)url {
   self = [super initWithType:type];
   if (self) {
@@ -82,7 +82,6 @@
     _title = [title copy];
     _subtitle = [subtitle copy];
     _URL = url;
-    _delegate = delegate;
   }
   return self;
 }
@@ -92,7 +91,7 @@
   if (self.hasImage && !self.imageFetched) {
     self.imageFetched = YES;
     // Fetch the image. During the fetch the cell's image should still be set.
-    [self.delegate loadImageForSuggestionItem:self];
+    [self.delegate loadImageForSuggestedItem:self];
   }
   [cell.faviconView configureWithAttributes:self.attributes];
   cell.titleLabel.text = self.title;
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item_unittest.mm b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item_unittest.mm
index 70b6647..807db2fb 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item_unittest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item_unittest.mm
@@ -25,18 +25,18 @@
   GURL url = GURL("http://chromium.org");
   NSString* publisher = @"publisherName";
   base::Time publishTime = base::Time::Now();
-  id delegateMock = OCMProtocolMock(@protocol(ContentSuggestionsItemDelegate));
+  id delegateMock = OCMProtocolMock(@protocol(SuggestedContentDelegate));
   ContentSuggestionsItem* item =
       [[ContentSuggestionsItem alloc] initWithType:0
                                              title:title
                                           subtitle:subtitle
-                                          delegate:delegateMock
                                                url:url];
+  item.delegate = delegateMock;
   item.hasImage = YES;
   item.publisher = publisher;
   item.publishDate = publishTime;
   item.availableOffline = YES;
-  OCMExpect([delegateMock loadImageForSuggestionItem:item]);
+  OCMExpect([delegateMock loadImageForSuggestedItem:item]);
   ContentSuggestionsCell* cell = [[[item cellClass] alloc] init];
   ASSERT_EQ([ContentSuggestionsCell class], [cell class]);
   ASSERT_EQ(url, item.URL);
@@ -64,25 +64,24 @@
   NSString* title = @"testTitle";
   NSString* subtitle = @"testSubtitle";
   GURL url = GURL("http://chromium.org");
-  id niceDelegateMock =
-      OCMProtocolMock(@protocol(ContentSuggestionsItemDelegate));
+  id niceDelegateMock = OCMProtocolMock(@protocol(SuggestedContentDelegate));
   ContentSuggestionsItem* item =
       [[ContentSuggestionsItem alloc] initWithType:0
                                              title:title
                                           subtitle:subtitle
-                                          delegate:niceDelegateMock
                                                url:url];
+  item.delegate = niceDelegateMock;
   item.hasImage = YES;
   item.image = [[UIImage alloc] init];
 
-  OCMExpect([niceDelegateMock loadImageForSuggestionItem:item]);
+  OCMExpect([niceDelegateMock loadImageForSuggestedItem:item]);
   ContentSuggestionsCell* cell = [[[item cellClass] alloc] init];
   ASSERT_NE(nil, item.image);
   [item configureCell:cell];
   ASSERT_OCMOCK_VERIFY(niceDelegateMock);
 
   id strictDelegateMock =
-      OCMStrictProtocolMock(@protocol(ContentSuggestionsItemDelegate));
+      OCMStrictProtocolMock(@protocol(SuggestedContentDelegate));
   item.delegate = strictDelegateMock;
   id cellMock = OCMPartialMock(cell);
   OCMExpect([cellMock setContentImage:item.image]);
@@ -103,14 +102,13 @@
   NSString* subtitle = @"testSubtitle";
   GURL url = GURL("http://chromium.org");
   // Strict mock. Raise exception if the load method is called.
-  id delegateMock =
-      OCMStrictProtocolMock(@protocol(ContentSuggestionsItemDelegate));
+  id delegateMock = OCMStrictProtocolMock(@protocol(SuggestedContentDelegate));
   ContentSuggestionsItem* item =
       [[ContentSuggestionsItem alloc] initWithType:0
                                              title:title
                                           subtitle:subtitle
-                                          delegate:delegateMock
                                                url:url];
+  item.delegate = delegateMock;
   item.hasImage = NO;
   ContentSuggestionsCell* cell = [[[item cellClass] alloc] init];
 
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h
index f2f7f974..5bf00530 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h
@@ -6,15 +6,16 @@
 #define IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CELLS_CONTENT_SUGGESTIONS_MOST_VISITED_ITEM_H_
 
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
-#import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h"
 #import "ios/third_party/material_components_ios/src/components/CollectionCells/src/MaterialCollectionCells.h"
 
 @class FaviconAttributes;
 @class FaviconViewNew;
+class GURL;
 
 // Item containing a Most Visited suggestion.
 @interface ContentSuggestionsMostVisitedItem
-    : CollectionViewItem<ContentSuggestionIdentification>
+    : CollectionViewItem<SuggestedContent>
 
 // Attributes to configure the favicon view.
 @property(nonatomic, strong, nonnull) FaviconAttributes* attributes;
@@ -22,6 +23,8 @@
 // Text for the title and the accessibility label of the cell.
 @property(nonatomic, copy, nonnull) NSString* title;
 
+@property(nonatomic, assign) GURL URL;
+
 @end
 
 // Associated cell to display a Most Visited tile based on the suggestion.
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.mm b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.mm
index 0e121d4..ab0c47f 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.mm
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.mm
@@ -4,10 +4,12 @@
 
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h"
 
+#import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
 #import "ios/chrome/browser/ui/favicon/favicon_attributes.h"
 #import "ios/chrome/browser/ui/favicon/favicon_view.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
 #import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
+#include "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -30,6 +32,9 @@
 @synthesize suggestionIdentifier = _suggestionIdentifier;
 @synthesize attributes = _attributes;
 @synthesize title = _title;
+@synthesize URL = _URL;
+@synthesize delegate = _delegate;
+@synthesize image = _image;
 
 - (instancetype)initWithType:(NSInteger)type {
   self = [super initWithType:type];
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.h b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.h
index f76b031c..f2b403f 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.h
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.h
@@ -8,11 +8,10 @@
 #import <UIKit/UIKit.h>
 
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
-#import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h"
 
 // Item displaying two text labels.
-@interface ContentSuggestionsTextItem
-    : CollectionViewItem<ContentSuggestionIdentification>
+@interface ContentSuggestionsTextItem : CollectionViewItem<SuggestedContent>
 
 @property(nonatomic, nullable, copy) NSString* text;
 @property(nonatomic, nullable, copy) NSString* detailText;
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.mm b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.mm
index 5cdf8c2..c27d2ca 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.mm
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.h"
 
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_cell.h"
+#import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
 #import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
 #import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
 
@@ -17,6 +18,9 @@
 @synthesize text = _text;
 @synthesize detailText = _detailText;
 @synthesize suggestionIdentifier = _suggestionIdentifier;
+@synthesize attributes = _attributes;
+@synthesize delegate = _delegate;
+@synthesize image = _image;
 
 - (instancetype)initWithType:(NSInteger)type {
   self = [super initWithType:type];
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h b/ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h
new file mode 100644
index 0000000..0145854
--- /dev/null
+++ b/ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CELLS_SUGGESTED_CONTENT_H_
+#define IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CELLS_SUGGESTED_CONTENT_H_
+
+#import <UIKit/UIKit.h>
+
+@class CollectionViewItem;
+@class ContentSuggestionIdentifier;
+@class FaviconAttributes;
+@protocol SuggestedContent;
+
+// Delegate for SuggestedContent.
+@protocol SuggestedContentDelegate
+
+// Loads the image associated with the |suggestedItem|.
+- (void)loadImageForSuggestedItem:
+    (CollectionViewItem<SuggestedContent>*)suggestedItem;
+
+@end
+
+// Behavior shared by the items in ContentSuggestions.
+@protocol SuggestedContent
+
+@property(nonatomic, weak) id<SuggestedContentDelegate> delegate;
+// Image associated with this content.
+@property(nonatomic, strong) UIImage* image;
+// Attributes for favicon.
+@property(nonatomic, strong) FaviconAttributes* attributes;
+// Identifier for this content.
+@property(nonatomic, strong) ContentSuggestionIdentifier* suggestionIdentifier;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CELLS_SUGGESTED_CONTENT_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestion.h b/ios/chrome/browser/ui/content_suggestions/content_suggestion.h
deleted file mode 100644
index 0e4e7b5e..0000000
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestion.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTION_H_
-#define IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTION_H_
-
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
-
-namespace base {
-class Time;
-}
-
-class GURL;
-
-// Enum defining the type of a ContentSuggestions.
-typedef NS_ENUM(NSInteger, ContentSuggestionType) {
-  // Use this type to pass information about an empty section. Suggestion of
-  // this type are empty and should not be displayed. The informations to be
-  // displayed are contained in the SectionInfo.
-  ContentSuggestionTypeEmpty,
-  ContentSuggestionTypeArticle,
-  ContentSuggestionTypeReadingList,
-  ContentSuggestionTypeMostVisited,
-};
-
-// Data for a suggestions item, compatible with Objective-C. Mostly acts as a
-// wrapper for ntp_snippets::ContentSuggestion.
-@interface ContentSuggestion : NSObject<ContentSuggestionIdentification>
-
-// Title of the suggestion.
-@property(nonatomic, copy, nullable) NSString* title;
-// Text for the suggestion.
-@property(nonatomic, copy, nullable) NSString* text;
-// URL associated with the suggestion.
-@property(nonatomic, assign) GURL url;
-// The name of the publisher.
-@property(nonatomic, copy, nullable) NSString* publisher;
-// The date of publication.
-@property(nonatomic, assign) base::Time publishDate;
-// Whether the suggestion is available offline.
-@property(nonatomic, assign) BOOL availableOffline;
-
-@property(nonatomic, assign) ContentSuggestionType type;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTION_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestion.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestion.mm
deleted file mode 100644
index 4b1e873..0000000
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestion.mm
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/content_suggestions/content_suggestion.h"
-
-#include "base/time/time.h"
-#include "url/gurl.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation ContentSuggestion
-
-@synthesize title = _title;
-@synthesize text = _text;
-@synthesize url = _url;
-@synthesize publisher = _publisher;
-@synthesize publishDate = _publishDate;
-@synthesize suggestionIdentifier = _suggestionIdentifier;
-@synthesize availableOffline = _availableOffline;
-@synthesize type = _type;
-
-@end
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h
index 1dd77ec..97b1567 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h
@@ -7,11 +7,22 @@
 
 #import <UIKit/UIKit.h>
 
-#import "ios/chrome/browser/ui/content_suggestions/content_suggestion.h"
-
 @class CollectionViewItem;
-@protocol ContentSuggestionsDataSource;
+@class ContentSuggestionsSectionInformation;
 @class ContentSuggestionsViewController;
+@protocol ContentSuggestionsDataSource;
+@protocol SuggestedContent;
+
+// Enum defining the type of a ContentSuggestions.
+typedef NS_ENUM(NSInteger, ContentSuggestionType) {
+  // Use this type to pass information about an empty section. Suggestion of
+  // this type are empty and should not be displayed. The informations to be
+  // displayed are contained in the SectionInfo.
+  ContentSuggestionTypeEmpty,
+  ContentSuggestionTypeArticle,
+  ContentSuggestionTypeReadingList,
+  ContentSuggestionTypeMostVisited,
+};
 
 // Updater for a CollectionViewController populating it with some items and
 // handling the items addition.
@@ -25,7 +36,7 @@
 
 // |collectionViewController| this Updater will update. Needs to be set before
 // adding items.
-@property(nonatomic, assign)
+@property(nonatomic, weak)
     ContentSuggestionsViewController* collectionViewController;
 
 // Returns whether the section should use the default, non-card style.
@@ -36,16 +47,18 @@
 
 // Adds the sections for the |suggestions| to the model and returns their
 // indices.
-- (NSIndexSet*)addSectionsForSuggestionsToModel:
-    (NSArray<ContentSuggestion*>*)suggestions;
+- (NSIndexSet*)addSectionsForSectionInfoToModel:
+    (NSArray<ContentSuggestionsSectionInformation*>*)sectionsInfo;
 
-// Adds the |suggestions| to the model and returns their index paths.
-// The caller must ensure the corresponding sections have been added to the
-// model.
-- (NSArray<NSIndexPath*>*)addSuggestionsToModel:
-    (NSArray<ContentSuggestion*>*)suggestions;
+// Adds the |suggestions| to the model in the section corresponding to
+// |sectionInfo| and returns their index paths. The caller must ensure the
+// corresponding section has been added to the model.
+- (NSArray<NSIndexPath*>*)
+addSuggestionsToModel:
+    (NSArray<CollectionViewItem<SuggestedContent>*>*)suggestions
+      withSectionInfo:(ContentSuggestionsSectionInformation*)sectionInfo;
 
-// Adds the empty item to this |section| and returns its index path. The updater
+// Adds an empty item to this |section| and returns its index path. The updater
 // does not do any check about the number of elements in the section.
 - (NSIndexPath*)addEmptyItemForSection:(NSInteger)section;
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm
index 089fa5d8..997ff50 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm
@@ -9,6 +9,7 @@
 #include "base/strings/sys_string_conversions.h"
 #include "base/time/time.h"
 #include "components/strings/grit/components_strings.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item+collection_view_controller.h"
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h"
 #import "ios/chrome/browser/ui/collection_view/collection_view_controller.h"
 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
@@ -16,7 +17,7 @@
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.h"
-#import "ios/chrome/browser/ui/content_suggestions/content_suggestion.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_data_sink.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_data_source.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_image_fetcher.h"
@@ -33,8 +34,7 @@
 
 namespace {
 
-using CSCollectionViewItem =
-    CollectionViewItem<ContentSuggestionIdentification>;
+using CSCollectionViewItem = CollectionViewItem<SuggestedContent>;
 using CSCollectionViewModel = CollectionViewModel<CSCollectionViewItem*>;
 
 // Enum defining the ItemType of this ContentSuggestionsCollectionUpdater.
@@ -45,8 +45,11 @@
   ItemTypeEmpty,
   ItemTypeReadingList,
   ItemTypeMostVisited,
+  ItemTypeUnknown,
 };
 
+// Enum defining the SectionIdentifier of this
+// ContentSuggestionsCollectionUpdater.
 typedef NS_ENUM(NSInteger, SectionIdentifier) {
   SectionIdentifierArticles = kSectionIdentifierEnumZero,
   SectionIdentifierReadingList,
@@ -54,20 +57,7 @@
   SectionIdentifierDefault,
 };
 
-// Update ContentSuggestionTypeForItemType if you update this function.
-ItemType ItemTypeForContentSuggestionType(ContentSuggestionType type) {
-  switch (type) {
-    case ContentSuggestionTypeArticle:
-      return ItemTypeArticle;
-    case ContentSuggestionTypeEmpty:
-      return ItemTypeEmpty;
-    case ContentSuggestionTypeReadingList:
-      return ItemTypeReadingList;
-    case ContentSuggestionTypeMostVisited:
-      return ItemTypeMostVisited;
-  }
-}
-
+// Returns the ContentSuggestionType associated with an ItemType |type|.
 ContentSuggestionType ContentSuggestionTypeForItemType(NSInteger type) {
   if (type == ItemTypeArticle)
     return ContentSuggestionTypeArticle;
@@ -84,15 +74,28 @@
 }
 
 // Returns the section identifier corresponding to the section |info|.
+ItemType ItemTypeForInfo(ContentSuggestionsSectionInformation* info) {
+  switch (info.sectionID) {
+    case ContentSuggestionsSectionArticles:
+      return ItemTypeArticle;
+    case ContentSuggestionsSectionReadingList:
+      return ItemTypeReadingList;
+    case ContentSuggestionsSectionMostVisited:
+      return ItemTypeMostVisited;
+
+    case ContentSuggestionsSectionUnknown:
+      return ItemTypeUnknown;
+  }
+}
+
+// Returns the section identifier corresponding to the section |info|.
 SectionIdentifier SectionIdentifierForInfo(
     ContentSuggestionsSectionInformation* info) {
   switch (info.sectionID) {
     case ContentSuggestionsSectionArticles:
       return SectionIdentifierArticles;
-
     case ContentSuggestionsSectionReadingList:
       return SectionIdentifierReadingList;
-
     case ContentSuggestionsSectionMostVisited:
       return SectionIdentifierMostVisited;
 
@@ -103,9 +106,8 @@
 
 }  // namespace
 
-@interface ContentSuggestionsCollectionUpdater ()<
-    ContentSuggestionsItemDelegate,
-    ContentSuggestionsDataSink>
+@interface ContentSuggestionsCollectionUpdater ()<ContentSuggestionsDataSink,
+                                                  SuggestedContentDelegate>
 
 @property(nonatomic, weak) id<ContentSuggestionsDataSource> dataSource;
 @property(nonatomic, strong)
@@ -157,7 +159,8 @@
   }
 
   [self.collectionViewController
-      addSuggestions:[self.dataSource suggestionsForSection:sectionInfo]];
+      addSuggestions:[self.dataSource itemsForSectionInfo:sectionInfo]
+       toSectionInfo:sectionInfo];
 }
 
 - (void)clearSuggestion:(ContentSuggestionIdentifier*)suggestionIdentifier {
@@ -193,9 +196,14 @@
 
   // The data is reset, add the new data directly in the model then reload the
   // collection.
-  NSArray<ContentSuggestion*>* suggestions = [self.dataSource allSuggestions];
-  [self addSectionsForSuggestionsToModel:suggestions];
-  [self addSuggestionsToModel:suggestions];
+  NSArray<ContentSuggestionsSectionInformation*>* sectionInfos =
+      [self.dataSource sectionsInfo];
+  [self addSectionsForSectionInfoToModel:sectionInfos];
+  for (ContentSuggestionsSectionInformation* sectionInfo in sectionInfos) {
+    [self
+        addSuggestionsToModel:[self.dataSource itemsForSectionInfo:sectionInfo]
+              withSectionInfo:sectionInfo];
+  }
   [self.collectionViewController.collectionView reloadData];
 }
 
@@ -230,135 +238,97 @@
   return ContentSuggestionTypeForItemType(item.type);
 }
 
-- (NSArray<NSIndexPath*>*)addSuggestionsToModel:
-    (NSArray<ContentSuggestion*>*)suggestions {
-  if (suggestions.count == 0) {
-    return [NSArray array];
-  }
+- (NSArray<NSIndexPath*>*)
+addSuggestionsToModel:(NSArray<CSCollectionViewItem*>*)suggestions
+      withSectionInfo:(ContentSuggestionsSectionInformation*)sectionInfo {
+  NSMutableArray<NSIndexPath*>* indexPaths = [NSMutableArray array];
 
   CSCollectionViewModel* model =
       self.collectionViewController.collectionViewModel;
-  NSMutableArray<NSIndexPath*>* indexPaths = [NSMutableArray array];
-  for (ContentSuggestion* suggestion in suggestions) {
-    ContentSuggestionsSectionInformation* sectionInfo =
-        suggestion.suggestionIdentifier.sectionInfo;
-    NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo);
+  NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo);
 
-    if (![model hasSectionForSectionIdentifier:sectionIdentifier])
-      continue;
-
-    NSInteger section = [model sectionForSectionIdentifier:sectionIdentifier];
-    NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
-
-    if (suggestion.type != ContentSuggestionTypeEmpty &&
-        [model hasItemAtIndexPath:indexPath] &&
-        [model itemAtIndexPath:indexPath].type == ItemTypeEmpty) {
-      [self.collectionViewController dismissEntryAtIndexPath:indexPath];
+  if (suggestions.count == 0) {
+    if ([model hasSectionForSectionIdentifier:sectionIdentifier] &&
+        [model numberOfItemsInSection:[model sectionForSectionIdentifier:
+                                                 sectionIdentifier]] == 0) {
+      [indexPaths
+          addObject:[self
+                        addEmptyItemForSection:[model
+                                                   sectionForSectionIdentifier:
+                                                       sectionIdentifier]]];
     }
-
-    switch (suggestion.type) {
-      case ContentSuggestionTypeEmpty: {
-        if ([model hasSectionForSectionIdentifier:sectionIdentifier] &&
-            [model numberOfItemsInSection:[model sectionForSectionIdentifier:
-                                                     sectionIdentifier]] == 0) {
-          CSCollectionViewItem* item =
-              [self emptyItemForSectionInfo:sectionInfo];
-          NSIndexPath* addedIndexPath =
-              [self addItem:item toSectionWithIdentifier:sectionIdentifier];
-          [indexPaths addObject:addedIndexPath];
-        }
-        break;
-      }
-      case ContentSuggestionTypeArticle: {
-        ContentSuggestionsItem* articleItem =
-            [self suggestionItemForSuggestion:suggestion];
-
-        articleItem.hasImage = YES;
-
-        __weak ContentSuggestionsItem* weakItem = articleItem;
-        [self fetchFaviconForItem:articleItem
-                          withURL:articleItem.URL
-                         callback:^void(FaviconAttributes* attributes) {
-                           weakItem.attributes = attributes;
-                         }];
-
-        __weak ContentSuggestionsCollectionUpdater* weakSelf = self;
-        [self.dataSource
-            fetchFaviconImageForSuggestion:articleItem.suggestionIdentifier
-                                completion:^void(UIImage* favicon) {
-                                  ContentSuggestionsCollectionUpdater*
-                                      strongSelf = weakSelf;
-                                  ContentSuggestionsItem* strongItem = weakItem;
-                                  if (!strongItem || !strongSelf)
-                                    return;
-
-                                  strongItem.attributes = [FaviconAttributes
-                                      attributesWithImage:favicon];
-                                  [strongSelf.collectionViewController
-                                      reconfigureCellsForItems:@[ strongItem ]];
-                                }];
-
-        NSIndexPath* addedIndexPath = [self addItem:articleItem
-                            toSectionWithIdentifier:sectionIdentifier];
-        [indexPaths addObject:addedIndexPath];
-        break;
-      }
-      case ContentSuggestionTypeReadingList: {
-        ContentSuggestionsItem* readingListItem =
-            [self suggestionItemForSuggestion:suggestion];
-
-        __weak ContentSuggestionsItem* weakItem = readingListItem;
-        [self fetchFaviconForItem:readingListItem
-                          withURL:readingListItem.URL
-                         callback:^void(FaviconAttributes* attributes) {
-                           weakItem.attributes = attributes;
-                         }];
-
-        NSIndexPath* addedIndexPath = [self addItem:readingListItem
-                            toSectionWithIdentifier:sectionIdentifier];
-        [indexPaths addObject:addedIndexPath];
-        break;
-      }
-      case ContentSuggestionTypeMostVisited: {
-        ContentSuggestionsMostVisitedItem* mostVisitedItem =
-            [[ContentSuggestionsMostVisitedItem alloc]
-                initWithType:ItemTypeMostVisited];
-        mostVisitedItem.title = suggestion.title;
-        [model addItem:mostVisitedItem
-            toSectionWithIdentifier:SectionIdentifierMostVisited];
-        [indexPaths addObject:indexPath];
-        break;
-      }
-    }
+    return indexPaths;
   }
 
+  BOOL emptyItemRemoved = NO;
+  NSArray<CSCollectionViewItem*>* existingItems =
+      [model itemsInSectionWithIdentifier:sectionIdentifier];
+  if (existingItems.count > 0 && existingItems[0].type == ItemTypeEmpty) {
+    [model removeItemWithType:ItemTypeEmpty
+        fromSectionWithIdentifier:sectionIdentifier];
+    emptyItemRemoved = YES;
+  }
+
+  [suggestions enumerateObjectsUsingBlock:^(CSCollectionViewItem* item,
+                                            NSUInteger index, BOOL* stop) {
+    ItemType type = ItemTypeForInfo(sectionInfo);
+    item.type = type;
+    NSIndexPath* addedIndexPath =
+        [self addItem:item toSectionWithIdentifier:sectionIdentifier];
+    [self fetchFaviconForItem:item];
+    item.delegate = self;
+
+    if (!emptyItemRemoved || index > 0) {
+      [indexPaths addObject:addedIndexPath];
+    } else {
+      [self.collectionViewController.collectionViewLayout invalidateLayout];
+    }
+  }];
+
   return indexPaths;
 }
 
-- (NSIndexSet*)addSectionsForSuggestionsToModel:
-    (NSArray<ContentSuggestion*>*)suggestions {
-  NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
+- (NSIndexSet*)addSectionsForSectionInfoToModel:
+    (NSArray<ContentSuggestionsSectionInformation*>*)sectionsInfo {
+  NSMutableIndexSet* addedSectionIdentifiers = [NSMutableIndexSet indexSet];
+  NSArray<ContentSuggestionsSectionInformation*>* orderedSectionsInfo =
+      [self.dataSource sectionsInfo];
 
   CSCollectionViewModel* model =
       self.collectionViewController.collectionViewModel;
-  for (ContentSuggestion* suggestion in suggestions) {
-    ContentSuggestionsSectionInformation* sectionInfo =
-        suggestion.suggestionIdentifier.sectionInfo;
+  for (ContentSuggestionsSectionInformation* sectionInfo in sectionsInfo) {
     NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo);
 
     if ([model hasSectionForSectionIdentifier:sectionIdentifier] ||
-        (suggestion.type == ContentSuggestionTypeEmpty &&
-         !sectionInfo.showIfEmpty)) {
+        (!sectionInfo.showIfEmpty &&
+         [self.dataSource itemsForSectionInfo:sectionInfo].count == 0)) {
       continue;
     }
 
-    [model addSectionWithIdentifier:sectionIdentifier];
-    self.sectionInfoBySectionIdentifier[@(sectionIdentifier)] = sectionInfo;
-    [indexSet addIndex:[model sectionForSectionIdentifier:sectionIdentifier]];
+    NSUInteger sectionIndex = 0;
+    for (ContentSuggestionsSectionInformation* orderedSectionInfo in
+             orderedSectionsInfo) {
+      NSInteger orderedSectionIdentifier =
+          SectionIdentifierForInfo(orderedSectionInfo);
+      if ([model hasSectionForSectionIdentifier:orderedSectionIdentifier]) {
+        sectionIndex++;
+      }
+    }
+    [model insertSectionWithIdentifier:sectionIdentifier atIndex:sectionIndex];
 
-    [self addHeader:sectionInfo];
+    self.sectionInfoBySectionIdentifier[@(sectionIdentifier)] = sectionInfo;
+    [addedSectionIdentifiers addIndex:sectionIdentifier];
+
+    [self addHeaderIfNeeded:sectionInfo];
     [self addFooterIfNeeded:sectionInfo];
   }
+
+  NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
+  [addedSectionIdentifiers enumerateIndexesUsingBlock:^(
+                               NSUInteger sectionIdentifier,
+                               BOOL* _Nonnull stop) {
+    [indexSet addIndex:[model sectionForSectionIdentifier:sectionIdentifier]];
+  }];
   return indexSet;
 }
 
@@ -373,26 +343,26 @@
   return [self addItem:item toSectionWithIdentifier:sectionIdentifier];
 }
 
-#pragma mark - ContentSuggestionsItemDelegate
+#pragma mark - SuggestedContentDelegate
 
-- (void)loadImageForSuggestionItem:(ContentSuggestionsItem*)suggestionItem {
+- (void)loadImageForSuggestedItem:(CSCollectionViewItem*)suggestedItem {
   __weak ContentSuggestionsCollectionUpdater* weakSelf = self;
-  __weak ContentSuggestionsItem* weakArticle = suggestionItem;
+  __weak CSCollectionViewItem* weakItem = suggestedItem;
 
   void (^imageFetchedCallback)(UIImage*) = ^(UIImage* image) {
     ContentSuggestionsCollectionUpdater* strongSelf = weakSelf;
-    ContentSuggestionsItem* strongArticle = weakArticle;
-    if (!strongSelf || !strongArticle) {
+    CSCollectionViewItem* strongItem = weakItem;
+    if (!strongSelf || !strongItem) {
       return;
     }
 
-    strongArticle.image = image;
+    strongItem.image = image;
     [strongSelf.collectionViewController
-        reconfigureCellsForItems:@[ strongArticle ]];
+        reconfigureCellsForItems:@[ strongItem ]];
   };
 
   [self.dataSource.imageFetcher
-      fetchImageForSuggestion:suggestionItem.suggestionIdentifier
+      fetchImageForSuggestion:suggestedItem.suggestionIdentifier
                      callback:imageFetchedCallback];
 }
 
@@ -420,12 +390,14 @@
   }
 }
 
-// Adds the header corresponding to |sectionInfo| to the section.
-- (void)addHeader:(ContentSuggestionsSectionInformation*)sectionInfo {
+// Adds the header corresponding to |sectionInfo| to the section if there is
+// none present and the section info contains a title.
+- (void)addHeaderIfNeeded:(ContentSuggestionsSectionInformation*)sectionInfo {
   NSInteger sectionIdentifier = SectionIdentifierForInfo(sectionInfo);
 
   if (![self.collectionViewController.collectionViewModel
-          headerForSectionWithIdentifier:sectionIdentifier]) {
+          headerForSectionWithIdentifier:sectionIdentifier] &&
+      sectionInfo.title) {
     CollectionViewTextItem* header =
         [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader];
     header.text = sectionInfo.title;
@@ -442,11 +414,58 @@
   self.sectionInfoBySectionIdentifier = [[NSMutableDictionary alloc] init];
 }
 
+// Fetches the favicon attributes for the |item|.
+- (void)fetchFaviconForItem:(CSCollectionViewItem*)item {
+  __weak ContentSuggestionsCollectionUpdater* weakSelf = self;
+  __weak CSCollectionViewItem* weakItem = item;
+
+  [self.dataSource
+      fetchFaviconAttributesForItem:item
+                         completion:^(FaviconAttributes* attributes) {
+                           ContentSuggestionsCollectionUpdater* strongSelf =
+                               weakSelf;
+                           CSCollectionViewItem* strongItem = weakItem;
+                           if (!strongSelf || !strongItem) {
+                             return;
+                           }
+
+                           strongItem.attributes = attributes;
+                           [strongSelf.collectionViewController
+                               reconfigureCellsForItems:@[ strongItem ]];
+
+                           [strongSelf fetchFaviconImageForItem:strongItem];
+                         }];
+}
+
+// Fetches the favicon image for the |item|.
+- (void)fetchFaviconImageForItem:(CSCollectionViewItem*)item {
+  __weak ContentSuggestionsCollectionUpdater* weakSelf = self;
+  __weak CSCollectionViewItem* weakItem = item;
+
+  [self.dataSource
+      fetchFaviconImageForItem:item
+                    completion:^(UIImage* image) {
+                      ContentSuggestionsCollectionUpdater* strongSelf =
+                          weakSelf;
+                      CSCollectionViewItem* strongItem = weakItem;
+                      if (!strongSelf || !strongItem || !image) {
+                        return;
+                      }
+
+                      strongItem.attributes =
+                          [FaviconAttributes attributesWithImage:image];
+                      [strongSelf.collectionViewController
+                          reconfigureCellsForItems:@[ strongItem ]];
+                    }];
+}
+
 // Runs the additional action for the section identified by |sectionInfo|.
 - (void)runAdditionalActionForSection:
     (ContentSuggestionsSectionInformation*)sectionInfo {
   SectionIdentifier sectionIdentifier = SectionIdentifierForInfo(sectionInfo);
 
+  // TODO(crbug.com/721229): Start spinner.
+
   NSMutableArray<ContentSuggestionIdentifier*>* knownSuggestionIdentifiers =
       [NSMutableArray array];
 
@@ -463,15 +482,23 @@
   [self.dataSource
       fetchMoreSuggestionsKnowing:knownSuggestionIdentifiers
                   fromSectionInfo:sectionInfo
-                         callback:^(NSArray<ContentSuggestion*>* suggestions) {
-                           [weakSelf moreSuggestionsFetched:suggestions];
+                         callback:^(
+                             NSArray<CSCollectionViewItem*>* suggestions) {
+                           [weakSelf moreSuggestionsFetched:suggestions
+                                              inSectionInfo:sectionInfo];
                          }];
 }
 
 // Adds the |suggestions| to the collection view. All the suggestions must have
 // the same sectionInfo.
-- (void)moreSuggestionsFetched:(NSArray<ContentSuggestion*>*)suggestions {
-  [self.collectionViewController addSuggestions:suggestions];
+- (void)moreSuggestionsFetched:(NSArray<CSCollectionViewItem*>*)suggestions
+                 inSectionInfo:
+                     (ContentSuggestionsSectionInformation*)sectionInfo {
+  if (suggestions) {
+    [self.collectionViewController addSuggestions:suggestions
+                                    toSectionInfo:sectionInfo];
+  }
+  // TODO(crbug.com/721229):Stop spinner.
 }
 
 // Returns a item to be displayed when the section identified by |sectionInfo|
@@ -486,52 +513,6 @@
   return item;
 }
 
-// Returns a suggestion item built with the |suggestion|.
-- (ContentSuggestionsItem*)suggestionItemForSuggestion:
-    (ContentSuggestion*)suggestion {
-  ContentSuggestionsItem* suggestionItem = [[ContentSuggestionsItem alloc]
-      initWithType:ItemTypeForContentSuggestionType(suggestion.type)
-             title:suggestion.title
-          subtitle:suggestion.text
-          delegate:self
-               url:suggestion.url];
-
-  suggestionItem.publisher = suggestion.publisher;
-  suggestionItem.publishDate = suggestion.publishDate;
-  suggestionItem.availableOffline = suggestion.availableOffline;
-
-  suggestionItem.suggestionIdentifier = suggestion.suggestionIdentifier;
-
-  return suggestionItem;
-}
-
-// Fetches the favicon associated with the |URL|, call the |callback| with the
-// attributes then reconfigure the |item|.
-- (void)fetchFaviconForItem:(CSCollectionViewItem*)item
-                    withURL:(const GURL&)URL
-                   callback:(void (^)(FaviconAttributes*))callback {
-  if (!callback)
-    return;
-
-  __weak ContentSuggestionsCollectionUpdater* weakSelf = self;
-  __weak CSCollectionViewItem* weakItem = item;
-  void (^completionBlock)(FaviconAttributes* attributes) =
-      ^(FaviconAttributes* attributes) {
-        CSCollectionViewItem* strongItem = weakItem;
-        ContentSuggestionsCollectionUpdater* strongSelf = weakSelf;
-        if (!strongSelf || !strongItem) {
-          return;
-        }
-
-        callback(attributes);
-
-        [strongSelf.collectionViewController
-            reconfigureCellsForItems:@[ strongItem ]];
-      };
-
-  [self.dataSource fetchFaviconAttributesForURL:URL completion:completionBlock];
-}
-
 // Adds |item| to |sectionIdentifier| section of the model of the
 // CollectionView. Returns the IndexPath of the newly added item.
 - (NSIndexPath*)addItem:(CSCollectionViewItem*)item
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater_unittest.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater_unittest.mm
index 71956f2..edfdf4f 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater_unittest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater_unittest.mm
@@ -5,7 +5,8 @@
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h"
 
 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
-#import "ios/chrome/browser/ui/content_suggestions/content_suggestion.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.h"
+#import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h"
 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h"
 #import "ios/chrome/browser/ui/content_suggestions/identifier/content_suggestions_section_information.h"
@@ -28,13 +29,16 @@
   OCMStub([mockCollection collectionViewModel]).andReturn(model);
   updater.collectionViewController = mockCollection;
 
-  ContentSuggestion* suggestion = [[ContentSuggestion alloc] init];
+  CollectionViewItem<SuggestedContent>* suggestion =
+      [[ContentSuggestionsTextItem alloc] initWithType:kItemTypeEnumZero];
   suggestion.suggestionIdentifier = [[ContentSuggestionIdentifier alloc] init];
   suggestion.suggestionIdentifier.sectionInfo =
       [[ContentSuggestionsSectionInformation alloc]
           initWithSectionID:ContentSuggestionsSectionArticles];
   suggestion.suggestionIdentifier.sectionInfo.showIfEmpty = YES;
-  [updater addSectionsForSuggestionsToModel:@[ suggestion ]];
+  [updater addSectionsForSectionInfoToModel:@[
+    suggestion.suggestionIdentifier.sectionInfo
+  ]];
   ASSERT_EQ(0, [model numberOfItemsInSection:0]);
 
   // Action.
@@ -54,14 +58,18 @@
   OCMStub([mockCollection collectionViewModel]).andReturn(model);
   updater.collectionViewController = mockCollection;
 
-  ContentSuggestion* suggestion = [[ContentSuggestion alloc] init];
+  CollectionViewItem<SuggestedContent>* suggestion =
+      [[ContentSuggestionsTextItem alloc] initWithType:kItemTypeEnumZero];
   suggestion.suggestionIdentifier = [[ContentSuggestionIdentifier alloc] init];
   suggestion.suggestionIdentifier.sectionInfo =
       [[ContentSuggestionsSectionInformation alloc]
           initWithSectionID:ContentSuggestionsSectionArticles];
   suggestion.suggestionIdentifier.sectionInfo.showIfEmpty = YES;
-  [updater addSectionsForSuggestionsToModel:@[ suggestion ]];
-  [updater addSuggestionsToModel:@[ suggestion ]];
+  [updater addSectionsForSectionInfoToModel:@[
+    suggestion.suggestionIdentifier.sectionInfo
+  ]];
+  [updater addSuggestionsToModel:@[ suggestion ]
+                 withSectionInfo:suggestion.suggestionIdentifier.sectionInfo];
   ASSERT_EQ(1, [model numberOfItemsInSection:0]);
 
   // Action.
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_data_source.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_data_source.h
index 671b7f1..5ea35b4 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_data_source.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_data_source.h
@@ -5,16 +5,20 @@
 #ifndef IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTIONS_DATA_SOURCE_H_
 #define IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTIONS_DATA_SOURCE_H_
 
+#import <UIKit/UIKit.h>
+
+@class CollectionViewItem;
 @class ContentSuggestion;
 @class ContentSuggestionIdentifier;
 @class ContentSuggestionsSectionInformation;
 @class FaviconAttributes;
 @protocol ContentSuggestionsDataSink;
 @protocol ContentSuggestionsImageFetcher;
-class GURL;
+@protocol SuggestedContent;
 
 // Typedef for a block taking the fetched suggestions as parameter.
-typedef void (^MoreSuggestionsFetched)(NSArray<ContentSuggestion*>* _Nonnull);
+typedef void (^MoreSuggestionsFetched)(
+    NSArray<CollectionViewItem<SuggestedContent>*>* _Nullable);
 
 // DataSource for the content suggestions. Provides the suggestions data in a
 // format compatible with Objective-C.
@@ -23,30 +27,30 @@
 // The data sink that will be notified when the data change.
 @property(nonatomic, nullable, weak) id<ContentSuggestionsDataSink> dataSink;
 
-// Returns all the data currently available.
-- (nonnull NSArray<ContentSuggestion*>*)allSuggestions;
+// Returns all the sections information in the order they should be displayed.
+- (nonnull NSArray<ContentSuggestionsSectionInformation*>*)sectionsInfo;
 
-// Returns the data currently available for the section identified by
-// |sectionInfo|.
-- (nonnull NSArray<ContentSuggestion*>*)suggestionsForSection:
+// Returns the items associated with the |sectionInfo|.
+- (nonnull NSArray<CollectionViewItem<SuggestedContent>*>*)itemsForSectionInfo:
     (nonnull ContentSuggestionsSectionInformation*)sectionInfo;
 
 // Returns an image updater for the suggestions provided by this data source.
 - (nullable id<ContentSuggestionsImageFetcher>)imageFetcher;
 
-// Fetches favicon attributes and calls the completion block.
-- (void)fetchFaviconAttributesForURL:(const GURL&)URL
-                          completion:
-                              (void (^_Nonnull)(FaviconAttributes* _Nonnull))
-                                  completion;
+// Fetches favicon attributes associated with the |item| and calls the
+// |completion| block.
+- (void)fetchFaviconAttributesForItem:
+            (nonnull CollectionViewItem<SuggestedContent>*)item
+                           completion:
+                               (void (^_Nonnull)(FaviconAttributes* _Nonnull))
+                                   completion;
 
-// Fetches favicon image associated with this |suggestion| in history. If it is
-// not present in the history, tries to download it. Calls the completion block
-// if an image has been found.
-// This can only be used for public URL.
+// Fetches the favicon image associated with the |item|. If there is no image
+// cached locally and the provider allows it, it tries to download it. The
+// |completion| block is only called if an image is found.
 - (void)
-fetchFaviconImageForSuggestion:(nonnull ContentSuggestionIdentifier*)suggestion
-                    completion:(void (^_Nonnull)(UIImage* _Nonnull))completion;
+fetchFaviconImageForItem:(nonnull CollectionViewItem<SuggestedContent>*)item
+              completion:(void (^_Nonnull)(UIImage* _Nonnull))completion;
 
 // Fetches additional content. All the |knownSuggestions| must come from the
 // same |sectionInfo|. If the fetch was completed, the given |callback| is
@@ -56,7 +60,7 @@
                     fromSectionInfo:
                         (nonnull ContentSuggestionsSectionInformation*)
                             sectionInfo
-                           callback:(nullable MoreSuggestionsFetched)callback;
+                           callback:(nonnull MoreSuggestionsFetched)callback;
 
 @end
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h
index b529127..88d7457 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h
@@ -9,10 +9,10 @@
 
 #import "ios/chrome/browser/ui/collection_view/collection_view_controller.h"
 
-@class ContentSuggestion;
+@class ContentSuggestionsSectionInformation;
 @protocol ContentSuggestionsCommands;
 @protocol ContentSuggestionsDataSource;
-@protocol ContentSuggestionIdentification;
+@protocol SuggestedContent;
 
 // CollectionViewController to display the suggestions items.
 @interface ContentSuggestionsViewController : CollectionViewController
@@ -29,15 +29,18 @@
     suggestionCommandHandler;
 // Override from superclass to have a more specific type.
 @property(nonatomic, readonly)
-    CollectionViewModel<CollectionViewItem<ContentSuggestionIdentification>*>*
+    CollectionViewModel<CollectionViewItem<SuggestedContent>*>*
         collectionViewModel;
 
 // Removes the entry at |indexPath|, from the collection and its model.
 - (void)dismissEntryAtIndexPath:(NSIndexPath*)indexPath;
 // Removes the |section|.
 - (void)dismissSection:(NSInteger)section;
-// Adds the |suggestions| to the collection and its model.
-- (void)addSuggestions:(NSArray<ContentSuggestion*>*)suggestions;
+// Adds the |suggestions| to the collection and its model in the section
+// corresponding to |sectionInfo|.
+- (void)addSuggestions:
+            (NSArray<CollectionViewItem<SuggestedContent>*>*)suggestions
+         toSectionInfo:(ContentSuggestionsSectionInformation*)sectionInfo;
 
 @end
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
index ba83a32a..5e7741ec 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
@@ -9,7 +9,6 @@
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_item.h"
-#import "ios/chrome/browser/ui/content_suggestions/content_suggestion.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_commands.h"
 #include "url/gurl.h"
@@ -18,6 +17,10 @@
 #error "This file requires ARC support."
 #endif
 
+namespace {
+using CSCollectionViewItem = CollectionViewItem<SuggestedContent>;
+}
+
 @interface ContentSuggestionsViewController ()
 
 @property(nonatomic, strong)
@@ -81,21 +84,23 @@
       }];
 }
 
-- (void)addSuggestions:(NSArray<ContentSuggestion*>*)suggestions {
+- (void)addSuggestions:(NSArray<CSCollectionViewItem*>*)suggestions
+         toSectionInfo:(ContentSuggestionsSectionInformation*)sectionInfo {
   if (suggestions.count == 0) {
     return;
   }
 
   [self.collectionView performBatchUpdates:^{
-    NSIndexSet* addedSections =
-        [self.collectionUpdater addSectionsForSuggestionsToModel:suggestions];
+    NSIndexSet* addedSections = [self.collectionUpdater
+        addSectionsForSectionInfoToModel:@[ sectionInfo ]];
     [self.collectionView insertSections:addedSections];
   }
                                 completion:nil];
 
   [self.collectionView performBatchUpdates:^{
     NSArray<NSIndexPath*>* addedItems =
-        [self.collectionUpdater addSuggestionsToModel:suggestions];
+        [self.collectionUpdater addSuggestionsToModel:suggestions
+                                      withSectionInfo:sectionInfo];
     [self.collectionView insertItemsAtIndexPaths:addedItems];
   }
                                 completion:nil];
diff --git a/ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h b/ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h
index a85ff6e5..5586e5e1 100644
--- a/ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h
+++ b/ios/chrome/browser/ui/content_suggestions/identifier/content_suggestion_identifier.h
@@ -20,11 +20,4 @@
 
 @end
 
-// A protocol for an object having an ID.
-@protocol ContentSuggestionIdentification
-
-@property(nonatomic, strong) ContentSuggestionIdentifier* suggestionIdentifier;
-
-@end
-
 #endif  // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_IDENTIFIER_CONTENT_SUGGESTION_IDENTIFIER_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/identifier/content_suggestions_section_information.h b/ios/chrome/browser/ui/content_suggestions/identifier/content_suggestions_section_information.h
index f2f2d8b4..e2aea5a 100644
--- a/ios/chrome/browser/ui/content_suggestions/identifier/content_suggestions_section_information.h
+++ b/ios/chrome/browser/ui/content_suggestions/identifier/content_suggestions_section_information.h
@@ -15,10 +15,8 @@
   ContentSuggestionsSectionLayoutCustom,
 };
 
-// This enum is used for ordering the sections and as ID for the section. Make
-// all sections in the same collection have different ID.
-// When adding a new kind of suggestions, add a new corresponding section. The
-// ordering is not persisted between launch, reordering is possible.
+// This enum is used for identifying the section. All section should have a
+// different ID.
 typedef NS_ENUM(NSInteger, ContentSuggestionsSectionID) {
   ContentSuggestionsSectionMostVisited = 0,
   ContentSuggestionsSectionArticles,
diff --git a/ios/chrome/browser/ui/elements/activity_overlay_coordinator.mm b/ios/chrome/browser/ui/elements/activity_overlay_coordinator.mm
index 8ff7dc09..f56daeec 100644
--- a/ios/chrome/browser/ui/elements/activity_overlay_coordinator.mm
+++ b/ios/chrome/browser/ui/elements/activity_overlay_coordinator.mm
@@ -4,11 +4,13 @@
 
 #import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
 
-#include "base/mac/objc_release_properties.h"
+#import "base/mac/objc_property_releaser.h"
 #import "ios/chrome/browser/ui/elements/activity_overlay_view_controller.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
 
-@interface ActivityOverlayCoordinator ()
+@interface ActivityOverlayCoordinator () {
+  base::mac::ObjCPropertyReleaser _propertyReleaser_ActivityOverlayCoordinator;
+}
 
 // View controller that displays an activity indicator.
 @property(nonatomic, retain) UIViewController* activityOverlayViewController;
@@ -18,9 +20,14 @@
 
 @synthesize activityOverlayViewController = _activityOverlayViewController;
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
+- (nullable instancetype)initWithBaseViewController:
+    (UIViewController*)viewController {
+  self = [super initWithBaseViewController:viewController];
+  if (self) {
+    _propertyReleaser_ActivityOverlayCoordinator.Init(
+        self, [ActivityOverlayCoordinator class]);
+  }
+  return self;
 }
 
 - (void)start {
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler.mm b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
index 1ea694b..ef7f08f 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
@@ -4,7 +4,7 @@
 
 #import "ios/chrome/browser/ui/main/browser_view_wrangler.h"
 
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
 #include "ios/chrome/browser/application_context.h"
@@ -27,6 +27,8 @@
 @interface BrowserViewWrangler ()<TabModelObserver> {
   ios::ChromeBrowserState* _browserState;
   __unsafe_unretained id<TabModelObserver> _tabModelObserver;
+
+  base::mac::ObjCPropertyReleaser _propertyReleaser_BrowserViewWrangler;
 }
 
 // Responsible for maintaining all state related to sharing to other devices.
@@ -66,6 +68,8 @@
 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
                     tabModelObserver:(id<TabModelObserver>)tabModelObserver {
   if ((self = [super init])) {
+    _propertyReleaser_BrowserViewWrangler.Init(self,
+                                               [BrowserViewWrangler class]);
     _browserState = browserState;
     _tabModelObserver = tabModelObserver;
   }
@@ -97,7 +101,6 @@
   [_mainTabModel browserStateDestroyed];
   [_otrTabModel browserStateDestroyed];
 
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_bar.mm b/ios/chrome/browser/ui/ntp/new_tab_page_bar.mm
index 5cedf9c..760902a 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_bar.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_bar.mm
@@ -8,7 +8,7 @@
 #include <cmath>
 
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_nsobject.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_bar_button.h"
@@ -66,6 +66,8 @@
   CGFloat buttonWidth_;
   // Percentage overlay sits over tab bar buttons.
   CGFloat overlayPercentage_;
+
+  base::mac::ObjCPropertyReleaser propertyReleaser_NewTabPageBar_;
 }
 
 @synthesize items = items_;
@@ -92,6 +94,7 @@
 }
 
 - (void)setup {
+  propertyReleaser_NewTabPageBar_.Init(self, [NewTabPageBar class]);
   self.selectedIndex = NSNotFound;
   canAnimate_ = NO;
   self.autoresizingMask =
@@ -130,11 +133,6 @@
   self.contentMode = UIViewContentModeRedraw;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (void)layoutSubviews {
   [super layoutSubviews];
 
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_bar_button.mm b/ios/chrome/browser/ui/ntp/new_tab_page_bar_button.mm
index 658f4256..91580eee 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_bar_button.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_bar_button.mm
@@ -5,7 +5,7 @@
 #import "ios/chrome/browser/ui/ntp/new_tab_page_bar_button.h"
 
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_bar_item.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
 #import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
@@ -27,6 +27,7 @@
 
   UIImage* _image;
   NSString* _title;
+  base::mac::ObjCPropertyReleaser _propertyReleaser_NewTabPageBarButton;
 }
 
 @property(nonatomic, retain) UIColor* color;
@@ -61,6 +62,8 @@
   DCHECK(item.image);
   NewTabPageBarButton* button =
       [[self class] buttonWithType:UIButtonTypeCustom];
+  button->_propertyReleaser_NewTabPageBarButton.Init(
+      button, [NewTabPageBarButton class]);
 
   button.title = item.title;
   button.image =
@@ -82,11 +85,6 @@
   return button;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (void)useIncognitoColorScheme:(CGFloat)percentage {
   DCHECK(percentage >= 0 && percentage <= 1);
   self.interpolatedColor =
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_bar_item.mm b/ios/chrome/browser/ui/ntp/new_tab_page_bar_item.mm
index 90075680..915497f3 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_bar_item.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_bar_item.mm
@@ -4,7 +4,7 @@
 
 #import "ios/chrome/browser/ui/ntp/new_tab_page_bar_item.h"
 
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 
 @implementation NewTabPageBarItem {
   // Title of the button.
@@ -15,6 +15,7 @@
   UIImage* image_;
   // New tab page view.
   __unsafe_unretained UIView* view_;  // weak
+  base::mac::ObjCPropertyReleaser propertyReleaser_NewTabPageBarItem_;
 }
 
 @synthesize title = title_;
@@ -34,9 +35,12 @@
   return item;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
+- (id)init {
+  self = [super init];
+  if (self) {
+    propertyReleaser_NewTabPageBarItem_.Init(self, [NewTabPageBarItem class]);
+  }
+  return self;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
index 43818934..60b9af7 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
@@ -8,7 +8,7 @@
 
 #import "base/ios/weak_nsobject.h"
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "components/prefs/pref_service.h"
@@ -131,6 +131,8 @@
   base::WeakNSProtocol<id<WebToolbarDelegate>> webToolbarDelegate_;
 
   base::scoped_nsobject<TabModel> tabModel_;
+
+  base::mac::ObjCPropertyReleaser propertyReleaser_NewTabPageController_;
 }
 
 // Load and bring panel into view.
@@ -196,6 +198,8 @@
   self = [super initWithNibName:nil url:url];
   if (self) {
     DCHECK(browserState);
+    propertyReleaser_NewTabPageController_.Init(self,
+                                                [NewTabPageController class]);
     browserState_ = browserState;
     loader_ = loader;
     newTabPageObserver_ = ntpObserver;
@@ -313,7 +317,6 @@
   [bookmarkController_ setDelegate:nil];
   [openTabsController_ setDelegate:nil];
   [[NSNotificationCenter defaultCenter] removeObserver:self];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_view.mm b/ios/chrome/browser/ui/ntp/new_tab_page_view.mm
index 95996a5..5245ee5 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_view.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_view.mm
@@ -5,7 +5,7 @@
 #import "ios/chrome/browser/ui/ntp/new_tab_page_view.h"
 
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_bar.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_bar_item.h"
 #import "ios/chrome/browser/ui/rtl_geometry.h"
@@ -17,6 +17,8 @@
   // subviews already.
   __unsafe_unretained NewTabPageBar* tabBar_;     // weak
   __unsafe_unretained UIScrollView* scrollView_;  // weak
+
+  base::mac::ObjCPropertyReleaser propertyReleaser_NewTabPageView_;
 }
 
 @synthesize scrollView = scrollView_;
@@ -27,6 +29,7 @@
                     andTabBar:(NewTabPageBar*)tabBar {
   self = [super initWithFrame:frame];
   if (self) {
+    propertyReleaser_NewTabPageView_.Init(self, [NewTabPageView class]);
     [self addSubview:scrollView];
     [self addSubview:tabBar];
     scrollView_ = scrollView;
@@ -45,11 +48,6 @@
   return nil;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (void)setFrame:(CGRect)frame {
   // When transitioning the iPhone xib to an iPad idiom, the setFrame call below
   // can sometimes fire a scrollViewDidScroll event which changes the
diff --git a/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm b/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm
index 5d26414f..b1049d4 100644
--- a/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm
+++ b/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm
@@ -8,7 +8,7 @@
 
 #include <algorithm>
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/metrics/histogram_macros.h"
 #import "ios/chrome/browser/ui/browser_view_controller.h"
@@ -168,6 +168,7 @@
   // The scrollview driving the OverscrollActionsController when not using
   // the scrollview from the CRWWebControllerObserver.
   base::scoped_nsobject<UIScrollView> _scrollview;
+  base::mac::ObjCPropertyReleaser _propertyReleaser_OverscrollActionsController;
 }
 
 // The view displayed over the header view holding the actions.
@@ -245,6 +246,8 @@
 - (instancetype)initWithScrollView:(UIScrollView*)scrollView {
   self = [super init];
   if (self) {
+    _propertyReleaser_OverscrollActionsController.Init(
+        self, [OverscrollActionsController class]);
     _overscrollActionView =
         [[OverscrollActionsView alloc] initWithFrame:CGRectZero];
     _overscrollActionView.delegate = self;
@@ -277,7 +280,6 @@
 - (void)dealloc {
   self.overscrollActionView.delegate = nil;
   [self invalidate];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_view.mm b/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_view.mm
index 4333cdb..9e2768c 100644
--- a/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_view.mm
+++ b/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_view.mm
@@ -7,7 +7,7 @@
 #import <QuartzCore/QuartzCore.h>
 
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_nsobject.h"
 #include "ios/chrome/browser/ui/rtl_geometry.h"
 #include "ios/chrome/browser/ui/uikit_ui_util.h"
@@ -134,6 +134,7 @@
   // The array is built the first time the method -layersToCenterVertically is
   // called.
   base::scoped_nsobject<NSArray> _layersToCenterVertically;
+  base::mac::ObjCPropertyReleaser _propertyReleaser_OverscrollActionsView;
 }
 
 // Redefined to readwrite.
@@ -241,6 +242,8 @@
 - (instancetype)initWithFrame:(CGRect)frame {
   self = [super initWithFrame:frame];
   if (self) {
+    _propertyReleaser_OverscrollActionsView.Init(self,
+                                                 [OverscrollActionsView class]);
     _deformationBehaviorEnabled = YES;
     self.autoresizingMask =
         UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
@@ -302,7 +305,6 @@
 
 - (void)dealloc {
   [self.snapshotView removeFromSuperview];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_controller.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_controller.mm
index 5d528dfa..6784433 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_controller.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_controller.mm
@@ -7,7 +7,7 @@
 #import "base/ios/weak_nsobject.h"
 #include "base/logging.h"
 #include "base/mac/bundle_locations.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #import "ios/chrome/browser/ui/animation_util.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_view.h"
 #include "ios/chrome/browser/ui/rtl_geometry.h"
@@ -55,6 +55,7 @@
 }  // anonymous namespace
 
 @interface PopupMenuController ()<PopupMenuViewDelegate> {
+  base::mac::ObjCPropertyReleaser propertyReleaser_PopupMenuController_;
   CGPoint sourceAnimationPoint_;
 }
 @end
@@ -84,6 +85,9 @@
   DCHECK(parent);
   self = [super init];
   if (self) {
+    propertyReleaser_PopupMenuController_.Init(self,
+                                               [PopupMenuController class]);
+
     popupContainer_ = [[PopupMenuView alloc]
         initWithFrame:CGRectMake(0, 0, kPopupContainerWidth,
                                  kPopupContainerHeight)];
@@ -172,7 +176,6 @@
   [popupContainer_ removeFromSuperview];
   [backgroundButton_ removeFromSuperview];
   [containerView_ removeFromSuperview];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/ui/settings/material_cell_catalog_view_controller.mm b/ios/chrome/browser/ui/settings/material_cell_catalog_view_controller.mm
index 924dde3..12b9a80 100644
--- a/ios/chrome/browser/ui/settings/material_cell_catalog_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/material_cell_catalog_view_controller.mm
@@ -731,7 +731,6 @@
           subtitle:@"Really, this is the best article I have ever seen, it "
                    @"is mandatory to read it! It describes how to write "
                    @"the best article."
-          delegate:nil
                url:GURL()];
   articleItem.publisher = @"Top Publisher.com";
   return articleItem;
diff --git a/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm b/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm
index 5475996..742b1c7 100644
--- a/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm
@@ -11,7 +11,7 @@
 #import "base/ios/weak_nsobject.h"
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
-#import "base/mac/objc_release_properties.h"
+#import "base/mac/objc_property_releaser.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/memory/ptr_util.h"
 #include "base/numerics/safe_conversions.h"
@@ -162,6 +162,9 @@
   // Module containing the reauthentication mechanism for viewing and copying
   // passwords.
   base::scoped_nsobject<ReauthenticationModule> reauthenticationModule_;
+
+  base::mac::ObjCPropertyReleaser
+      propertyReleaser_SavePasswordsCollectionViewController_;
 }
 // Kick off async request to get logins from password store.
 - (void)getLoginsFromPasswordStore;
@@ -191,13 +194,15 @@
     [self getLoginsFromPasswordStore];
     [self updateEditButton];
     [self loadModel];
+
+    propertyReleaser_SavePasswordsCollectionViewController_.Init(
+        self, [SavePasswordsCollectionViewController class]);
   }
   return self;
 }
 
 - (void)dealloc {
   [passwordManagerEnabled_ setObserver:nil];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/ui/settings/settings_root_collection_view_controller.mm b/ios/chrome/browser/ui/settings/settings_root_collection_view_controller.mm
index adbdd0a..d259ef5 100644
--- a/ios/chrome/browser/ui/settings/settings_root_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_root_collection_view_controller.mm
@@ -7,7 +7,7 @@
 #include "base/ios/ios_util.h"
 #include "base/logging.h"
 #import "base/mac/foundation_util.h"
-#import "base/mac/objc_release_properties.h"
+#import "base/mac/objc_property_releaser.h"
 #import "base/mac/scoped_nsobject.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
@@ -39,15 +39,22 @@
   SavedBarButtomItemPositionEnum savedBarButtonItemPosition_;
   base::scoped_nsobject<UIBarButtonItem> savedBarButtonItem_;
   base::scoped_nsobject<UIView> veil_;
+
+  base::mac::ObjCPropertyReleaser
+      propertyReleaser_SettingsRootCollectionViewController_;
 }
 
 @synthesize shouldHideDoneButton = shouldHideDoneButton_;
 @synthesize collectionViewAccessibilityIdentifier =
     collectionViewAccessibilityIdentifier_;
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
+- (instancetype)initWithStyle:(CollectionViewControllerStyle)style {
+  self = [super initWithStyle:style];
+  if (self) {
+    propertyReleaser_SettingsRootCollectionViewController_.Init(
+        self, [SettingsRootCollectionViewController class]);
+  }
+  return self;
 }
 
 - (void)viewDidLoad {
diff --git a/ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.mm b/ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.mm
index 9744b78..2ccd5b3 100644
--- a/ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.mm
@@ -8,7 +8,7 @@
 
 #include "base/i18n/time_formatting.h"
 #include "base/mac/foundation_util.h"
-#import "base/mac/objc_release_properties.h"
+#import "base/mac/objc_property_releaser.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
 #include "components/browser_sync/profile_sync_service.h"
@@ -65,6 +65,8 @@
   std::unique_ptr<SyncObserverBridge> syncObserver_;
   std::unique_ptr<OAuth2TokenServiceObserverBridge> tokenServiceObserver_;
   base::scoped_nsobject<UITextField> passphrase_;
+  base::mac::ObjCPropertyReleaser
+      propertyReleaser_SyncEncryptionPassphraseCollectionViewController_;
 }
 
 // Sets up the navigation bar's right button. The button will be enabled iff
@@ -140,16 +142,14 @@
     tokenServiceObserver_.reset(new OAuth2TokenServiceObserverBridge(
         OAuth2TokenServiceFactory::GetForBrowserState(browserState_), self));
 
+    propertyReleaser_SyncEncryptionPassphraseCollectionViewController_.Init(
+        self, [SyncEncryptionPassphraseCollectionViewController class]);
+
     [self loadModel];
   }
   return self;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (UITextField*)passphrase {
   return passphrase_;
 }
diff --git a/ios/chrome/browser/ui/stack_view/card_view.mm b/ios/chrome/browser/ui/stack_view/card_view.mm
index 62a810a..0ca24d4 100644
--- a/ios/chrome/browser/ui/stack_view/card_view.mm
+++ b/ios/chrome/browser/ui/stack_view/card_view.mm
@@ -26,7 +26,7 @@
 #include <algorithm>
 
 #import "base/mac/foundation_util.h"
-#include "base/mac/objc_release_properties.h"
+#import "base/mac/objc_property_releaser.h"
 #import "base/mac/scoped_nsobject.h"
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ui/animation_util.h"
@@ -124,7 +124,9 @@
 
 @end
 
-@implementation CardTabView
+@implementation CardTabView {
+  base::mac::ObjCPropertyReleaser _propertyReleaser_CardTabView;
+}
 
 #pragma mark - Property Implementation
 
@@ -144,6 +146,7 @@
   if (!self)
     return self;
 
+  _propertyReleaser_CardTabView.Init(self, [CardTabView class]);
   _isIncognito = isIncognito;
 
   UIImage* image = ImageWithName(@"default_favicon", _isIncognito);
@@ -175,11 +178,6 @@
   return nil;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (void)setCloseButtonSide:(CardCloseButtonSide)closeButtonSide {
   if (_closeButtonSide != closeButtonSide) {
     _closeButtonSide = closeButtonSide;
diff --git a/ios/chrome/browser/ui/stack_view/stack_view_controller.mm b/ios/chrome/browser/ui/stack_view/stack_view_controller.mm
index d17b5d6..cdb4ba2b 100644
--- a/ios/chrome/browser/ui/stack_view/stack_view_controller.mm
+++ b/ios/chrome/browser/ui/stack_view/stack_view_controller.mm
@@ -16,7 +16,7 @@
 #include "base/logging.h"
 #import "base/mac/bundle_locations.h"
 #import "base/mac/foundation_util.h"
-#include "base/mac/objc_release_properties.h"
+#import "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_block.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/metrics/histogram_macros.h"
@@ -489,6 +489,8 @@
   // |YES| if there is card set animation being processed. For testing only.
   // Save last touch point used by new tab animation.
   CGPoint _lastTapPoint;
+
+  base::mac::ObjCPropertyReleaser _propertyReleaserStackViewController;
 }
 
 @synthesize activeCardSet = _activeCardSet;
@@ -511,6 +513,8 @@
   DCHECK(activeCardSet == otrCardSet || activeCardSet == mainCardSet);
   self = [super initWithNibName:nil bundle:nil];
   if (self) {
+    _propertyReleaserStackViewController.Init(self,
+                                              [StackViewController class]);
     [self setUpWithMainCardSet:mainCardSet
                     otrCardSet:otrCardSet
                  activeCardSet:activeCardSet];
@@ -812,7 +816,6 @@
   [_mainCardSet setObserver:nil];
   [_otrCardSet setObserver:nil];
   [self cleanUpViewsAndNotifications];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
index 7f3f0c4..c09b68a3 100644
--- a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
+++ b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
@@ -12,7 +12,7 @@
 #import "base/ios/weak_nsobject.h"
 #include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
@@ -183,6 +183,8 @@
   // The model index of the placeholder gap, if one exists.  This value is used
   // as the new model index of the dragged tab when it is dropped.
   NSUInteger _placeholderGapModelIndex;
+
+  base::mac::ObjCPropertyReleaser _propertyReleaser_TabStripController;
 }
 
 @property(nonatomic, readonly, retain) TabStripView* tabStripView;
@@ -324,6 +326,7 @@
 - (instancetype)initWithTabModel:(TabModel*)tabModel
                            style:(TabStrip::Style)style {
   if ((self = [super init])) {
+    _propertyReleaser_TabStripController.Init(self, [TabStripController class]);
     _tabArray.reset([[NSMutableArray alloc] initWithCapacity:10]);
     _closingTabs.reset([[NSMutableSet alloc] initWithCapacity:5]);
 
@@ -429,7 +432,6 @@
   [_tabStripView setDelegate:nil];
   [_tabStripView setLayoutDelegate:nil];
   [_tabModel removeObserver:self];
-  base::mac::ReleaseProperties(self);
   [super dealloc];
 }
 
diff --git a/ios/chrome/browser/ui/tabs/tab_view.mm b/ios/chrome/browser/ui/tabs/tab_view.mm
index 00a5502..cbc9b10 100644
--- a/ios/chrome/browser/ui/tabs/tab_view.mm
+++ b/ios/chrome/browser/ui/tabs/tab_view.mm
@@ -8,7 +8,7 @@
 #include "base/i18n/rtl.h"
 #include "base/ios/ios_util.h"
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/strings/sys_string_conversions.h"
 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
@@ -72,6 +72,8 @@
   BOOL _collapsed;
 
   base::scoped_nsobject<MDCActivityIndicator> _activityIndicator;
+
+  base::mac::ObjCPropertyReleaser _propertyReleaser_TabView;
 }
 @end
 
@@ -113,6 +115,7 @@
 
 - (id)initWithEmptyView:(BOOL)emptyView selected:(BOOL)selected {
   if ((self = [super initWithFrame:CGRectZero])) {
+    _propertyReleaser_TabView.Init(self, [TabView class]);
     [self setOpaque:NO];
     [self createCommonViews];
     // -setSelected only calls -updateBackgroundImage if the selected state
@@ -127,11 +130,6 @@
   return self;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (void)setSelected:(BOOL)selected {
   BOOL wasSelected = [self isSelected];
   [super setSelected:selected];
diff --git a/ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.mm b/ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.mm
index 36f9c5f..e6bd954 100644
--- a/ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.mm
+++ b/ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.mm
@@ -9,7 +9,7 @@
 #include "base/ios/ios_util.h"
 #import "base/ios/weak_nsobject.h"
 #include "base/logging.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/metrics/field_trial.h"
 #include "components/strings/grit/components_strings.h"
@@ -105,6 +105,7 @@
 @interface ToolsMenuViewController ()<UICollectionViewDelegateFlowLayout,
                                       UICollectionViewDataSource,
                                       ReadingListMenuNotificationDelegate> {
+  base::mac::ObjCPropertyReleaser _propertyReleaser_ToolsMenuViewController;
   BOOL _waitForInk;
   // Weak pointer to ReadingListMenuNotifier, used to set the starting values
   // for the reading list badge.
@@ -335,14 +336,11 @@
 }
 
 - (void)commonInitialization {
+  _propertyReleaser_ToolsMenuViewController.Init(
+      self, [ToolsMenuViewController class]);
   _readingListMenuNotifier.reset();
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (void)loadView {
   [super loadView];
 
diff --git a/ios/chrome/browser/ui/tools_menu/tools_menu_view_item.mm b/ios/chrome/browser/ui/tools_menu/tools_menu_view_item.mm
index 182983a..9db0a61 100644
--- a/ios/chrome/browser/ui/tools_menu/tools_menu_view_item.mm
+++ b/ios/chrome/browser/ui/tools_menu/tools_menu_view_item.mm
@@ -5,7 +5,7 @@
 #import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
 
 #include "base/i18n/rtl.h"
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -16,7 +16,9 @@
 static NSString* const kMenuItemCellID = @"MenuItemCellID";
 }
 
-@implementation ToolsMenuViewItem
+@implementation ToolsMenuViewItem {
+  base::mac::ObjCPropertyReleaser _propertyReleaser_ToolsMenuViewItem;
+}
 
 @synthesize accessibilityIdentifier = _accessibilityIdentifier;
 @synthesize active = _active;
@@ -27,17 +29,13 @@
 - (id)init {
   self = [super init];
   if (self) {
+    _propertyReleaser_ToolsMenuViewItem.Init(self, [ToolsMenuViewItem class]);
     _active = YES;
   }
 
   return self;
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 + (NSString*)cellID {
   return kMenuItemCellID;
 }
@@ -60,7 +58,9 @@
 
 @end
 
-@implementation ToolsMenuViewCell
+@implementation ToolsMenuViewCell {
+  base::mac::ObjCPropertyReleaser _propertyReleaser_ToolsMenuViewCell;
+}
 
 @synthesize title = _title;
 @synthesize horizontalMargin = _horizontalMargin;
@@ -82,17 +82,13 @@
 }
 
 - (void)commonInitialization {
+  _propertyReleaser_ToolsMenuViewCell.Init(self, [ToolsMenuViewCell class]);
   _horizontalMargin = !base::i18n::IsRTL() ? kToolsMenuItemHorizontalMargin
                                            : kToolsMenuItemHorizontalMarginRTL;
   [self setBackgroundColor:[UIColor whiteColor]];
   [self setOpaque:YES];
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (void)prepareForReuse {
   [super prepareForReuse];
   [_title setText:nil];
diff --git a/ios/chrome/browser/ui/tools_menu/tools_menu_view_tools_cell.mm b/ios/chrome/browser/ui/tools_menu/tools_menu_view_tools_cell.mm
index da5cc4a..e5c3b10 100644
--- a/ios/chrome/browser/ui/tools_menu/tools_menu_view_tools_cell.mm
+++ b/ios/chrome/browser/ui/tools_menu/tools_menu_view_tools_cell.mm
@@ -4,7 +4,7 @@
 
 #import "ios/chrome/browser/ui/tools_menu/tools_menu_view_tools_cell.h"
 
-#include "base/mac/objc_release_properties.h"
+#include "base/mac/objc_property_releaser.h"
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/ui/commands/ios_command_ids.h"
 #include "ios/chrome/browser/ui/rtl_geometry.h"
@@ -18,7 +18,9 @@
 // IDC_MinimumLabelValue) to avoid collisions.
 #define IDC_TEMP_EDIT_BOOKMARK 3900
 
-@implementation ToolsMenuViewToolsCell
+@implementation ToolsMenuViewToolsCell {
+  base::mac::ObjCPropertyReleaser _propertyReleaser_ToolsMenuViewToolsCell;
+}
 
 @synthesize reloadButton = _reloadButton;
 @synthesize shareButton = _shareButton;
@@ -44,6 +46,9 @@
 }
 
 - (void)commonInitialization {
+  _propertyReleaser_ToolsMenuViewToolsCell.Init(self,
+                                                [ToolsMenuViewToolsCell class]);
+
   [self setBackgroundColor:[UIColor whiteColor]];
   [self setOpaque:YES];
 
@@ -97,11 +102,6 @@
   [self addConstraints];
 }
 
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 - (UIButton*)newButtonForImageIds:(int[2][3])imageIds
                         commandID:(int)commandID
              accessibilityLabelID:(int)labelID
diff --git a/ios/clean/chrome/app/steps/root_coordinator+application_step.mm b/ios/clean/chrome/app/steps/root_coordinator+application_step.mm
index c6041a24..fea3ab4 100644
--- a/ios/clean/chrome/app/steps/root_coordinator+application_step.mm
+++ b/ios/clean/chrome/app/steps/root_coordinator+application_step.mm
@@ -5,12 +5,14 @@
 #import "ios/clean/chrome/app/steps/root_coordinator+application_step.h"
 
 #include "base/memory/ptr_util.h"
-#import "base/supports_user_data.h"
+#include "base/supports_user_data.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/clean/chrome/app/application_state.h"
 #import "ios/shared/chrome/browser/ui/browser_list/browser.h"
 #import "ios/shared/chrome/browser/ui/browser_list/browser_list.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.h"
 #import "ios/shared/chrome/browser/ui/coordinators/browser_coordinator+internal.h"
 #import "ios/web/public/navigation_manager.h"
 #include "ios/web/public/web_state/web_state.h"
@@ -49,16 +51,16 @@
   self.browser =
       BrowserList::FromBrowserState(state.browserState)->CreateNewBrowser();
 
-  // PLACEHOLDER: Create some open, empty web states.
-  WebStateList& webStateList = self.browser->web_state_list();
-  for (int i = 0; i < 7; i++) {
+  BrowserListSessionService* service =
+      BrowserListSessionServiceFactory::GetForBrowserState(state.browserState);
+
+  if (!service || !service->RestoreSession()) {
+    WebStateList& webStateList = self.browser->web_state_list();
     web::WebState::CreateParams webStateCreateParams(
         self.browser->browser_state());
-    std::unique_ptr<web::WebState> webState =
-        web::WebState::Create(webStateCreateParams);
-    webStateList.InsertWebState(0, std::move(webState));
+    webStateList.InsertWebState(0, web::WebState::Create(webStateCreateParams));
+    webStateList.ActivateWebStateAt(0);
   }
-  webStateList.ActivateWebStateAt(0);
 
   [self start];
 
diff --git a/ios/shared/chrome/browser/ui/browser_list/BUILD.gn b/ios/shared/chrome/browser/ui/browser_list/BUILD.gn
index 5e01070..43231579 100644
--- a/ios/shared/chrome/browser/ui/browser_list/BUILD.gn
+++ b/ios/shared/chrome/browser/ui/browser_list/BUILD.gn
@@ -10,17 +10,27 @@
     "browser_list.mm",
     "browser_list_observer.h",
     "browser_list_observer.mm",
+    "browser_list_session_service.h",
+    "browser_list_session_service_factory.h",
+    "browser_list_session_service_factory.mm",
+    "browser_list_session_service_impl.h",
+    "browser_list_session_service_impl.mm",
     "browser_web_state_list_delegate.h",
     "browser_web_state_list_delegate.mm",
   ]
   deps = [
     "//base",
+    "//components/keyed_service/core",
+    "//components/keyed_service/ios",
+    "//ios/chrome/browser",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/find_in_page",
     "//ios/chrome/browser/sessions",
+    "//ios/chrome/browser/sessions:serialisation",
     "//ios/chrome/browser/ssl",
     "//ios/chrome/browser/web_state_list",
     "//ios/shared/chrome/browser/ui/commands",
+    "//ios/web",
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
 }
diff --git a/ios/shared/chrome/browser/ui/browser_list/DEPS b/ios/shared/chrome/browser/ui/browser_list/DEPS
new file mode 100644
index 0000000..1a098df
--- /dev/null
+++ b/ios/shared/chrome/browser/ui/browser_list/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+components/keyed_service/core",
+  "+components/keyed_service/ios",
+]
diff --git a/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service.h b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service.h
new file mode 100644
index 0000000..c4803b2
--- /dev/null
+++ b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service.h
@@ -0,0 +1,34 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_SHARED_CHROME_BROWSER_UI_BROWSER_LIST_BROWSER_LIST_SESSION_SERVICE_H_
+#define IOS_SHARED_CHROME_BROWSER_UI_BROWSER_LIST_BROWSER_LIST_SESSION_SERVICE_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+// BrowserListSessionService allow saving and restoring saved sessions.
+class BrowserListSessionService : public KeyedService {
+ public:
+  BrowserListSessionService() = default;
+  ~BrowserListSessionService() override = default;
+
+  // Restores the saved session on the current thread. Returns whether the
+  // load was successful or not.
+  virtual bool RestoreSession() = 0;
+
+  // Schedules deletion of the file containing the last session.
+  virtual void ScheduleLastSessionDeletion() = 0;
+
+  // Schedules recording the session on a background thread. If |immediately|
+  // is false, then the session is recorded after a delay. In all cases, if
+  // another session recording was previously scheduled with a delay, that will
+  // be cancelled.
+  virtual void ScheduleSaveSession(bool immediately) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BrowserListSessionService);
+};
+
+#endif  // IOS_SHARED_CHROME_BROWSER_UI_BROWSER_LIST_BROWSER_LIST_SESSION_SERVICE_H_
diff --git a/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.h b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.h
new file mode 100644
index 0000000..f2d20de
--- /dev/null
+++ b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.h
@@ -0,0 +1,47 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_SHARED_CHROME_BROWSER_UI_BROWSER_LIST_BROWSER_LIST_SESSION_SERVICE_FACTORY_H_
+#define IOS_SHARED_CHROME_BROWSER_UI_BROWSER_LIST_BROWSER_LIST_SESSION_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/ios/browser_state_keyed_service_factory.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace ios {
+class ChromeBrowserState;
+}
+
+class BrowserListSessionService;
+
+// BrowserListSessionServiceFactory attaches BrowserListSessionService to
+// ChromeBrowserState.
+class BrowserListSessionServiceFactory
+    : public BrowserStateKeyedServiceFactory {
+ public:
+  static BrowserListSessionService* GetForBrowserState(
+      ios::ChromeBrowserState* browser_state);
+  static BrowserListSessionServiceFactory* GetInstance();
+
+ private:
+  friend struct base::DefaultSingletonTraits<BrowserListSessionServiceFactory>;
+
+  BrowserListSessionServiceFactory();
+  ~BrowserListSessionServiceFactory() override;
+
+  // BrowserStateKeyedServiceFactory implementation.
+  std::unique_ptr<KeyedService> BuildServiceInstanceFor(
+      web::BrowserState* context) const override;
+  web::BrowserState* GetBrowserStateToUse(
+      web::BrowserState* context) const override;
+  bool ServiceIsNULLWhileTesting() const override;
+
+  DISALLOW_COPY_AND_ASSIGN(BrowserListSessionServiceFactory);
+};
+
+#endif  // IOS_SHARED_CHROME_BROWSER_UI_BROWSER_LIST_BROWSER_LIST_SESSION_SERVICE_FACTORY_H_
diff --git a/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.mm b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.mm
new file mode 100644
index 0000000..f97f03b
--- /dev/null
+++ b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.mm
@@ -0,0 +1,89 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/singleton.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/keyed_service/ios/browser_state_dependency_manager.h"
+#include "ios/chrome/browser/browser_state/browser_state_otr_helper.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/sessions/session_service_ios.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_impl.h"
+#import "ios/web/public/certificate_policy_cache.h"
+#import "ios/web/public/web_state/session_certificate_policy_cache.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+// WebState factory for BrowserListSessionServiceImpl. The factory is called
+// to create a WebState from serialised session state, so the browser state
+// certificate policy cache is updated with the information from the WebState.
+std::unique_ptr<web::WebState> CreateWebState(
+    ios::ChromeBrowserState* browser_state,
+    CRWSessionStorage* session_storage) {
+  std::unique_ptr<web::WebState> web_state =
+      web::WebState::CreateWithStorageSession(
+          web::WebState::CreateParams(browser_state), session_storage);
+  web_state->GetSessionCertificatePolicyCache()->UpdateCertificatePolicyCache(
+      web::BrowserState::GetCertificatePolicyCache(browser_state));
+  return web_state;
+}
+}
+
+// static
+BrowserListSessionService* BrowserListSessionServiceFactory::GetForBrowserState(
+    ios::ChromeBrowserState* browser_state) {
+  return static_cast<BrowserListSessionService*>(
+      GetInstance()->GetServiceForBrowserState(browser_state, true));
+}
+
+// static
+BrowserListSessionServiceFactory*
+BrowserListSessionServiceFactory::GetInstance() {
+  return base::Singleton<BrowserListSessionServiceFactory>::get();
+}
+
+BrowserListSessionServiceFactory::BrowserListSessionServiceFactory()
+    : BrowserStateKeyedServiceFactory(
+          "BrowserListSessionService",
+          BrowserStateDependencyManager::GetInstance()) {}
+
+BrowserListSessionServiceFactory::~BrowserListSessionServiceFactory() {}
+
+std::unique_ptr<KeyedService>
+BrowserListSessionServiceFactory::BuildServiceInstanceFor(
+    web::BrowserState* context) const {
+  ios::ChromeBrowserState* browser_state =
+      ios::ChromeBrowserState::FromBrowserState(context);
+  // It is safe to use base::Unretained here as BrowserListSessionServiceImpl
+  // will be destroyed before the ChromeBrowserState (as it is a KeyedService).
+  return base::MakeUnique<BrowserListSessionServiceImpl>(
+      BrowserList::FromBrowserState(browser_state),
+      base::SysUTF8ToNSString(browser_state->GetStatePath().AsUTF8Unsafe()),
+      [SessionServiceIOS sharedService],
+      base::BindRepeating(&CreateWebState, base::Unretained(browser_state)));
+}
+
+web::BrowserState* BrowserListSessionServiceFactory::GetBrowserStateToUse(
+    web::BrowserState* context) const {
+  // The off-the-record ChromeBrowserState has its own BrowserListSessionService
+  // to implement restoring the sessions after the application has been evicted
+  // while in the background.
+  return GetBrowserStateOwnInstanceInIncognito(context);
+}
+
+bool BrowserListSessionServiceFactory::ServiceIsNULLWhileTesting() const {
+  // Avoid automatically saving session during unit tests.
+  return true;
+}
diff --git a/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_impl.h b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_impl.h
new file mode 100644
index 0000000..ed69309
--- /dev/null
+++ b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_impl.h
@@ -0,0 +1,60 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_SHARED_CHROME_BROWSER_UI_BROWSER_LIST_BROWSER_LIST_SESSION_SERVICE_IMPL_H_
+#define IOS_SHARED_CHROME_BROWSER_UI_BROWSER_LIST_BROWSER_LIST_SESSION_SERVICE_IMPL_H_
+
+#import <Foundation/Foundation.h>
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service.h"
+#import "ios/web/public/web_state/web_state.h"
+
+class BrowserList;
+class BrowserListObserver;
+@class CRWSessionStorage;
+@class SessionServiceIOS;
+
+namespace web {
+class WebState;
+}
+
+// Implementation of BrowserListSessionService that automatically track the
+// active WebStates of |browser_list| and save the session when the active
+// WebState changes or a new navigation item is committed.
+class BrowserListSessionServiceImpl : public BrowserListSessionService {
+ public:
+  // Callback used to create WebStates when restoring a session.
+  using CreateWebStateCallback =
+      base::RepeatingCallback<std::unique_ptr<web::WebState>(
+          CRWSessionStorage*)>;
+
+  BrowserListSessionServiceImpl(BrowserList* browser_list,
+                                NSString* session_directory,
+                                SessionServiceIOS* session_service,
+                                const CreateWebStateCallback& create_web_state);
+  ~BrowserListSessionServiceImpl() override;
+
+  // BrowserListSessionService implementation.
+  bool RestoreSession() override;
+  void ScheduleLastSessionDeletion() override;
+  void ScheduleSaveSession(bool immediately) override;
+
+  // KeyedService implementation.
+  void Shutdown() override;
+
+ private:
+  BrowserList* browser_list_;
+  __strong NSString* session_directory_;
+  __strong SessionServiceIOS* session_service_;
+  CreateWebStateCallback create_web_state_;
+  std::unique_ptr<BrowserListObserver> observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(BrowserListSessionServiceImpl);
+};
+
+#endif  // IOS_SHARED_CHROME_BROWSER_UI_BROWSER_LIST_BROWSER_LIST_SESSION_SERVICE_IMPL_H_
diff --git a/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_impl.mm b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_impl.mm
new file mode 100644
index 0000000..4604cdf
--- /dev/null
+++ b/ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_impl.mm
@@ -0,0 +1,291 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_impl.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "ios/chrome/browser/chrome_url_constants.h"
+#import "ios/chrome/browser/sessions/session_ios.h"
+#import "ios/chrome/browser/sessions/session_service_ios.h"
+#import "ios/chrome/browser/sessions/session_window_ios.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/chrome/browser/web_state_list/web_state_list_observer.h"
+#import "ios/chrome/browser/web_state_list/web_state_list_serialization.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list_observer.h"
+#import "ios/web/public/navigation_item.h"
+#import "ios/web/public/navigation_manager.h"
+#import "ios/web/public/web_state/web_state.h"
+#import "ios/web/public/web_state/web_state_observer.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// BrowserListSessionServiceWebStateObserver observes a WebState and invokes
+// |closure| when a new navigation item is committed.
+class BrowserListSessionServiceWebStateObserver : public web::WebStateObserver {
+ public:
+  explicit BrowserListSessionServiceWebStateObserver(
+      const base::RepeatingClosure& closure);
+  ~BrowserListSessionServiceWebStateObserver() override;
+
+  // Changes the observed WebState to |web_state|.
+  void ObserveWebState(web::WebState* web_state);
+
+  // web::WebStateObserver implementation.
+  void NavigationItemCommitted(
+      const web::LoadCommittedDetails& load_details) override;
+
+ private:
+  base::RepeatingClosure closure_;
+
+  DISALLOW_COPY_AND_ASSIGN(BrowserListSessionServiceWebStateObserver);
+};
+
+BrowserListSessionServiceWebStateObserver::
+    BrowserListSessionServiceWebStateObserver(
+        const base::RepeatingClosure& closure)
+    : WebStateObserver(), closure_(closure) {
+  DCHECK(!closure_.is_null());
+}
+
+BrowserListSessionServiceWebStateObserver::
+    ~BrowserListSessionServiceWebStateObserver() = default;
+
+void BrowserListSessionServiceWebStateObserver::ObserveWebState(
+    web::WebState* web_state) {
+  WebStateObserver::Observe(web_state);
+}
+
+void BrowserListSessionServiceWebStateObserver::NavigationItemCommitted(
+    const web::LoadCommittedDetails& load_details) {
+  closure_.Run();
+}
+
+// BrowserListSessionServiceWebStateListObserver observes a WebStateList and
+// invokes |closure| when the active WebState changes or a navigation item is
+// committed in the active WebState.
+class BrowserListSessionServiceWebStateListObserver
+    : public WebStateListObserver {
+ public:
+  BrowserListSessionServiceWebStateListObserver(
+      WebStateList* web_state_list,
+      const base::RepeatingClosure& closure);
+  ~BrowserListSessionServiceWebStateListObserver() override;
+
+  // WebStateListObserver implementation.
+  void WebStateActivatedAt(WebStateList* web_state_list,
+                           web::WebState* old_web_state,
+                           web::WebState* new_web_state,
+                           int active_index,
+                           bool user_action) override;
+
+ private:
+  WebStateList* web_state_list_;
+  base::RepeatingClosure closure_;
+  BrowserListSessionServiceWebStateObserver observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(BrowserListSessionServiceWebStateListObserver);
+};
+
+BrowserListSessionServiceWebStateListObserver::
+    BrowserListSessionServiceWebStateListObserver(
+        WebStateList* web_state_list,
+        const base::RepeatingClosure& closure)
+    : web_state_list_(web_state_list), closure_(closure), observer_(closure) {
+  DCHECK(!closure_.is_null());
+  web_state_list_->AddObserver(this);
+  if (web_state_list_->active_index() != WebStateList::kInvalidIndex) {
+    WebStateActivatedAt(web_state_list_, nullptr,
+                        web_state_list_->GetActiveWebState(),
+                        web_state_list_->active_index(), false);
+  }
+}
+
+BrowserListSessionServiceWebStateListObserver::
+    ~BrowserListSessionServiceWebStateListObserver() {
+  web_state_list_->RemoveObserver(this);
+}
+
+void BrowserListSessionServiceWebStateListObserver::WebStateActivatedAt(
+    WebStateList* web_state_list,
+    web::WebState* old_web_state,
+    web::WebState* new_web_state,
+    int active_index,
+    bool user_action) {
+  if (old_web_state)
+    closure_.Run();
+  observer_.ObserveWebState(new_web_state);
+}
+
+// BrowserListSessionServiceBrowserListObserver observes a BrowserList and
+// invokes |closure| then the active WebState changes in any of the Browsers'
+// WebStates or a navigation item is committed in one of the active WebStates.
+class BrowserListSessionServiceBrowserListObserver
+    : public BrowserListObserver {
+ public:
+  BrowserListSessionServiceBrowserListObserver(
+      BrowserList* browser_list,
+      const base::RepeatingClosure& closure);
+  ~BrowserListSessionServiceBrowserListObserver() override;
+
+  // BrowserListObserver implementation.
+  void OnBrowserCreated(BrowserList* browser_list, Browser* browser) override;
+  void OnBrowserRemoved(BrowserList* browser_list, Browser* browser) override;
+
+ private:
+  BrowserList* browser_list_;
+  base::RepeatingClosure closure_;
+  std::map<Browser*, std::unique_ptr<WebStateListObserver>> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(BrowserListSessionServiceBrowserListObserver);
+};
+
+BrowserListSessionServiceBrowserListObserver::
+    BrowserListSessionServiceBrowserListObserver(
+        BrowserList* browser_list,
+        const base::RepeatingClosure& closure)
+    : browser_list_(browser_list), closure_(closure) {
+  DCHECK(!closure_.is_null());
+  for (int index = 0; index < browser_list_->count(); ++index)
+    OnBrowserCreated(browser_list_, browser_list_->GetBrowserAtIndex(index));
+  browser_list_->AddObserver(this);
+}
+
+BrowserListSessionServiceBrowserListObserver::
+    ~BrowserListSessionServiceBrowserListObserver() {
+  browser_list_->RemoveObserver(this);
+}
+
+void BrowserListSessionServiceBrowserListObserver::OnBrowserCreated(
+    BrowserList* browser_list,
+    Browser* browser) {
+  DCHECK(browser);
+  DCHECK_EQ(browser_list, browser_list_);
+  DCHECK(observers_.find(browser) == observers_.end());
+  observers_.insert(std::make_pair(
+      browser, base::MakeUnique<BrowserListSessionServiceWebStateListObserver>(
+                   &browser->web_state_list(), closure_)));
+}
+
+void BrowserListSessionServiceBrowserListObserver::OnBrowserRemoved(
+    BrowserList* browser_list,
+    Browser* browser) {
+  DCHECK(browser);
+  DCHECK_EQ(browser_list, browser_list_);
+  DCHECK(observers_.find(browser) != observers_.end());
+  observers_.erase(browser);
+}
+
+}  // namespace
+
+BrowserListSessionServiceImpl::BrowserListSessionServiceImpl(
+    BrowserList* browser_list,
+    NSString* session_directory,
+    SessionServiceIOS* session_service,
+    const CreateWebStateCallback& create_web_state)
+    : browser_list_(browser_list),
+      session_directory_([session_directory copy]),
+      session_service_(session_service),
+      create_web_state_(create_web_state) {
+  DCHECK(browser_list_);
+  DCHECK(session_directory_);
+  DCHECK(session_service_);
+  DCHECK(create_web_state_);
+  // It is safe to use base::Unretained as the closure is indirectly owned by
+  // the BrowserListSessionServiceImpl instance and will be deleted before it.
+  observer_ = base::MakeUnique<BrowserListSessionServiceBrowserListObserver>(
+      browser_list,
+      base::BindRepeating(&BrowserListSessionServiceImpl::ScheduleSaveSession,
+                          base::Unretained(this), false));
+}
+
+BrowserListSessionServiceImpl::~BrowserListSessionServiceImpl() = default;
+
+bool BrowserListSessionServiceImpl::RestoreSession() {
+  DCHECK(browser_list_) << "RestoreSession called after Shutdown.";
+  SessionIOS* session =
+      [session_service_ loadSessionFromDirectory:session_directory_];
+  if (!session)
+    return false;
+
+  DCHECK_LE(session.sessionWindows.count, static_cast<NSUInteger>(INT_MAX));
+  for (NSUInteger index = 0; index < session.sessionWindows.count; ++index) {
+    Browser* browser =
+        static_cast<int>(index) < browser_list_->count()
+            ? browser_list_->GetBrowserAtIndex(static_cast<int>(index))
+            : browser_list_->CreateNewBrowser();
+
+    SessionWindowIOS* session_window = session.sessionWindows[index];
+    if (!session_window.sessions.count)
+      continue;
+
+    WebStateList& web_state_list = browser->web_state_list();
+    const int old_count = web_state_list.count();
+    DeserializeWebStateList(&web_state_list, session_window, create_web_state_);
+    DCHECK_GT(web_state_list.count(), old_count);
+
+    // If there was a single tab open without any navigation, then close it
+    // after restoring the tabs.
+    if (old_count != 1)
+      continue;
+
+    web::WebState* web_state = web_state_list.GetWebStateAt(0);
+    web::NavigationManager* navigation_manager =
+        web_state->GetNavigationManager();
+
+    if (navigation_manager->CanGoBack())
+      continue;
+
+    web::NavigationItem* navigation_item =
+        navigation_manager->GetLastCommittedItem();
+    if (!navigation_item)
+      continue;
+
+    if (navigation_item->GetURL() != kChromeUINewTabURL)
+      continue;
+
+    web_state_list.CloseWebStateAt(0);
+  }
+
+  return true;
+}
+
+void BrowserListSessionServiceImpl::ScheduleLastSessionDeletion() {
+  DCHECK(browser_list_) << "ScheduleLastSessionDeletion called after Shutdown.";
+  [session_service_ deleteLastSessionFileInDirectory:session_directory_];
+}
+
+void BrowserListSessionServiceImpl::ScheduleSaveSession(bool immediately) {
+  DCHECK(browser_list_) << "ScheduleSaveSession called after Shutdown.";
+  DCHECK_GE(browser_list_->count(), 0);
+  const int count = browser_list_->count();
+  NSMutableArray<SessionWindowIOS*>* windows =
+      [NSMutableArray arrayWithCapacity:static_cast<NSUInteger>(count)];
+  for (int index = 0; index < count; ++index) {
+    Browser* browser = browser_list_->GetBrowserAtIndex(index);
+    [windows addObject:SerializeWebStateList(&browser->web_state_list())];
+  }
+
+  [session_service_ saveSession:[[SessionIOS alloc] initWithWindows:windows]
+                      directory:session_directory_
+                    immediately:immediately];
+}
+
+void BrowserListSessionServiceImpl::Shutdown() {
+  // Stop observing the BrowserList before it is destroyed (BrowserList is a
+  // base::SupportsUserData::Data attached to ChromeBrowserState so it will
+  // be destroyed after the KeyedService two-stage shutdown).
+  browser_list_ = nullptr;
+  observer_.reset();
+}
diff --git a/ios/web/navigation/window_location_inttest.mm b/ios/web/navigation/window_location_inttest.mm
index efd90c2c..d6821a5 100644
--- a/ios/web/navigation/window_location_inttest.mm
+++ b/ios/web/navigation/window_location_inttest.mm
@@ -205,7 +205,7 @@
 #if TARGET_IPHONE_SIMULATOR
 #define MAYBE_WindowLocationReload WindowLocationReload
 #else
-#define MAYBE_WindowLocationReload FLAKY_WindowLocationReload
+#define MAYBE_WindowLocationReload DISABLED_WindowLocationReload
 #endif
 // TODO(crbug.com/721465): Enable this test on device.
 TEST_F(WindowLocationTest, MAYBE_WindowLocationReload) {
diff --git a/ios/web/public/origin_util_unittest.mm b/ios/web/public/origin_util_unittest.mm
index 723ebf7..79cae2b0 100644
--- a/ios/web/public/origin_util_unittest.mm
+++ b/ios/web/public/origin_util_unittest.mm
@@ -6,7 +6,7 @@
 
 #import <WebKit/WebKit.h>
 
-#include "base/mac/objc_release_properties.h"
+#import "base/mac/objc_property_releaser.h"
 #import "base/mac/scoped_nsobject.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -19,13 +19,17 @@
 @property(nonatomic) NSInteger port;
 @end
 
-@implementation WKSecurityOriginStub
+@implementation WKSecurityOriginStub {
+  base::mac::ObjCPropertyReleaser _propertyReleaser;
+}
 @synthesize protocol = _protocol;
 @synthesize host = _host;
 @synthesize port = _port;
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
+- (instancetype)init {
+  if (self = [super init]) {
+    _propertyReleaser.Init(self, [WKSecurityOriginStub class]);
+  }
+  return self;
 }
 @end
 
diff --git a/ios/web/public/test/test_web_thread_bundle.h b/ios/web/public/test/test_web_thread_bundle.h
index dc3497c..49eb03fa 100644
--- a/ios/web/public/test/test_web_thread_bundle.h
+++ b/ios/web/public/test/test_web_thread_bundle.h
@@ -12,16 +12,16 @@
 // first member variable in test classes, so it is destroyed last, and the test
 // threads always exist from the perspective of other classes.
 //
-// By default, all of the created TestWebThreads and the task scheduler will
-// be backed by a single shared MessageLoop. If a test truly needs separate
-// threads, it can do so by passing the appropriate combination of option values
-// during the TestWebThreadBundle construction.
+// By default, all of the created TestWebThreads will be backed by a single
+// shared MessageLoop. If a test truly needs separate threads, it can do so by
+// passing the appropriate combination of option values during the
+// TestWebThreadBundle construction.
 //
-// To synchronously run tasks posted to task scheduler or to TestWebThreads
-// that use the shared MessageLoop, call RunLoop::Run/RunUntilIdle() on the
-// thread where the TestWebThreadBundle lives. The destructor of
-// TestWebThreadBundle runs remaining TestWebThreads tasks, remaining
-// blocking pool tasks, and remaining BLOCK_SHUTDOWN task scheduler tasks.
+// To synchronously run tasks posted to TestWebThreads that use the shared
+// MessageLoop, call RunLoop::Run/RunUntilIdle() on the thread where the
+// TestWebThreadBundle lives. The destructor of TestWebThreadBundle runs
+// remaining TestWebThreads tasks, remaining blocking pool tasks, and remaining
+// BLOCK_SHUTDOWN task scheduler tasks.
 //
 // Some tests using the IO thread expect a MessageLoopForIO. Passing
 // IO_MAINLOOP will use a MessageLoopForIO for the main MessageLoop.
@@ -35,7 +35,6 @@
 class MessageLoop;
 namespace test {
 class ScopedAsyncTaskScheduler;
-class ScopedTaskScheduler;
 }  // namespace test
 }  // namespace base
 
@@ -54,7 +53,6 @@
     REAL_DB_THREAD = 1 << 1,
     REAL_FILE_THREAD = 1 << 2,
     REAL_IO_THREAD = 1 << 3,
-    REAL_TASK_SCHEDULER = 1 << 4,
   };
 
   TestWebThreadBundle();
@@ -68,7 +66,6 @@
   std::unique_ptr<base::MessageLoop> message_loop_;
   std::unique_ptr<base::test::ScopedAsyncTaskScheduler>
       scoped_async_task_scheduler_;
-  std::unique_ptr<base::test::ScopedTaskScheduler> scoped_task_scheduler_;
   std::unique_ptr<TestWebThread> ui_thread_;
   std::unique_ptr<TestWebThread> db_thread_;
   std::unique_ptr<TestWebThread> file_thread_;
diff --git a/ios/web/test/test_web_thread_bundle.cc b/ios/web/test/test_web_thread_bundle.cc
index 14eb656..e6d776e4 100644
--- a/ios/web/test/test_web_thread_bundle.cc
+++ b/ios/web/test/test_web_thread_bundle.cc
@@ -8,7 +8,6 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_async_task_scheduler.h"
-#include "base/test/scoped_task_scheduler.h"
 #include "ios/web/public/test/test_web_thread.h"
 #include "ios/web/web_thread_impl.h"
 
@@ -53,7 +52,6 @@
   base::RunLoop().RunUntilIdle();
 
   scoped_async_task_scheduler_.reset();
-  scoped_task_scheduler_.reset();
 }
 
 void TestWebThreadBundle::Init(int options) {
@@ -65,13 +63,8 @@
 
   ui_thread_.reset(new TestWebThread(WebThread::UI, message_loop_.get()));
 
-  if (options & REAL_TASK_SCHEDULER) {
-    scoped_async_task_scheduler_ =
-        base::MakeUnique<base::test::ScopedAsyncTaskScheduler>();
-  } else {
-    scoped_task_scheduler_ =
-        base::MakeUnique<base::test::ScopedTaskScheduler>(message_loop_.get());
-  }
+  scoped_async_task_scheduler_ =
+      base::MakeUnique<base::test::ScopedAsyncTaskScheduler>();
 
   if (options & TestWebThreadBundle::REAL_DB_THREAD) {
     db_thread_.reset(new TestWebThread(WebThread::DB));
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 1322901..894043784 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -25,7 +25,7 @@
 #import "base/mac/bind_objc_block.h"
 #include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
-#include "base/mac/objc_release_properties.h"
+#import "base/mac/objc_property_releaser.h"
 #include "base/mac/scoped_cftyperef.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/memory/ptr_util.h"
@@ -216,7 +216,10 @@
 // A container object for any navigation information that is only available
 // during pre-commit delegate callbacks, and thus must be held until the
 // navigation commits and the informatino can be used.
-@interface CRWWebControllerPendingNavigationInfo : NSObject
+@interface CRWWebControllerPendingNavigationInfo : NSObject {
+  base::mac::ObjCPropertyReleaser
+      _propertyReleaser_CRWWebControllerPendingNavigationInfo;
+}
 // The referrer for the page.
 @property(nonatomic, copy) NSString* referrer;
 // The MIME type for the page.
@@ -241,16 +244,12 @@
 
 - (instancetype)init {
   if ((self = [super init])) {
+    _propertyReleaser_CRWWebControllerPendingNavigationInfo.Init(
+        self, [CRWWebControllerPendingNavigationInfo class]);
     _navigationType = WKNavigationTypeOther;
   }
   return self;
 }
-
-- (void)dealloc {
-  base::mac::ReleaseProperties(self);
-  [super dealloc];
-}
-
 @end
 
 @interface CRWWebController ()<CRWContextMenuDelegate,
diff --git a/mojo/public/cpp/system/wait_set.cc b/mojo/public/cpp/system/wait_set.cc
index 1728f81b..75ce93e 100644
--- a/mojo/public/cpp/system/wait_set.cc
+++ b/mojo/public/cpp/system/wait_set.cc
@@ -271,12 +271,9 @@
               Context* context) {
     base::AutoLock lock(lock_);
 
-    // This could be a cancellation notification following an explicit
-    // RemoveHandle(), in which case we really don't care and don't want to
-    // add it to the ready set. Only update and signal if that's not the case.
-    if (!handle_to_context_.count(handle)) {
-      DCHECK_EQ(MOJO_RESULT_CANCELLED, result);
-    } else {
+    // This notification may have raced with RemoveHandle() from another thread.
+    // We only signal the WaitSet if that's not the case.
+    if (handle_to_context_.count(handle)) {
       ready_handles_[handle] = {result, signals_state};
       handle_event_.Signal();
     }
diff --git a/net/http/http_stream_factory_impl.cc b/net/http/http_stream_factory_impl.cc
index 2579933..669f04f 100644
--- a/net/http/http_stream_factory_impl.cc
+++ b/net/http/http_stream_factory_impl.cc
@@ -194,6 +194,8 @@
 void HttpStreamFactoryImpl::PreconnectStreams(
     int num_streams,
     const HttpRequestInfo& request_info) {
+  DCHECK(request_info.url.is_valid());
+
   AddJobControllerCountToHistograms();
 
   SSLConfig server_ssl_config;
diff --git a/remoting/client/chromoting_client.cc b/remoting/client/chromoting_client.cc
index 8029477..ced714a 100644
--- a/remoting/client/chromoting_client.cc
+++ b/remoting/client/chromoting_client.cc
@@ -40,6 +40,8 @@
 }
 
 ChromotingClient::~ChromotingClient() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
   if (signal_strategy_)
       signal_strategy_->RemoveListener(this);
 }
diff --git a/remoting/client/client_telemetry_logger.cc b/remoting/client/client_telemetry_logger.cc
index 908a527..3240c69 100644
--- a/remoting/client/client_telemetry_logger.cc
+++ b/remoting/client/client_telemetry_logger.cc
@@ -36,7 +36,9 @@
     ChromotingEvent::Mode mode)
     : mode_(mode), log_writer_(log_writer) {}
 
-ClientTelemetryLogger::~ClientTelemetryLogger() {}
+ClientTelemetryLogger::~ClientTelemetryLogger() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
 
 void ClientTelemetryLogger::SetHostInfo(const std::string& host_version,
                                         ChromotingEvent::Os host_os,
diff --git a/remoting/client/display/fake_canvas.cc b/remoting/client/display/fake_canvas.cc
index 167fe36b..fb7fde6b 100644
--- a/remoting/client/display/fake_canvas.cc
+++ b/remoting/client/display/fake_canvas.cc
@@ -8,7 +8,9 @@
 
 FakeCanvas::FakeCanvas() : weak_factory_(this) {}
 
-FakeCanvas::~FakeCanvas() {}
+FakeCanvas::~FakeCanvas() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
 
 void FakeCanvas::Clear() {}
 
diff --git a/remoting/client/display/gl_cursor.cc b/remoting/client/display/gl_cursor.cc
index 113de25..cabb758ec 100644
--- a/remoting/client/display/gl_cursor.cc
+++ b/remoting/client/display/gl_cursor.cc
@@ -20,7 +20,9 @@
 
 GlCursor::GlCursor() : weak_factory_(this) {}
 
-GlCursor::~GlCursor() {}
+GlCursor::~GlCursor() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
 
 void GlCursor::SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) {
   int data_size = cursor_shape.width() * cursor_shape.height() *
diff --git a/remoting/client/display/gl_cursor_feedback.cc b/remoting/client/display/gl_cursor_feedback.cc
index 97804211..0e6fbfb 100644
--- a/remoting/client/display/gl_cursor_feedback.cc
+++ b/remoting/client/display/gl_cursor_feedback.cc
@@ -45,7 +45,9 @@
 
 GlCursorFeedback::GlCursorFeedback() : weak_factory_(this) {}
 
-GlCursorFeedback::~GlCursorFeedback() {}
+GlCursorFeedback::~GlCursorFeedback() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
 
 void GlCursorFeedback::SetCanvas(base::WeakPtr<Canvas> canvas) {
   if (!canvas) {
diff --git a/remoting/client/display/gl_desktop.cc b/remoting/client/display/gl_desktop.cc
index 41cb4632..9362bd3 100644
--- a/remoting/client/display/gl_desktop.cc
+++ b/remoting/client/display/gl_desktop.cc
@@ -59,7 +59,9 @@
 
 GlDesktop::GlDesktop() : weak_factory_(this) {}
 
-GlDesktop::~GlDesktop() {}
+GlDesktop::~GlDesktop() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
 
 void GlDesktop::SetCanvas(base::WeakPtr<Canvas> canvas) {
   last_desktop_size_.set(0, 0);
diff --git a/remoting/client/display/gl_renderer.cc b/remoting/client/display/gl_renderer.cc
index 8b2ee23..2913ff3 100644
--- a/remoting/client/display/gl_renderer.cc
+++ b/remoting/client/display/gl_renderer.cc
@@ -33,7 +33,9 @@
   thread_checker_.DetachFromThread();
 }
 
-GlRenderer::~GlRenderer() {}
+GlRenderer::~GlRenderer() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
 
 void GlRenderer::SetDelegate(base::WeakPtr<GlRendererDelegate> delegate) {
   DCHECK(!delegate_);
diff --git a/remoting/client/plugin/pepper_video_renderer_2d.cc b/remoting/client/plugin/pepper_video_renderer_2d.cc
index 4a605cb..730ddf6 100644
--- a/remoting/client/plugin/pepper_video_renderer_2d.cc
+++ b/remoting/client/plugin/pepper_video_renderer_2d.cc
@@ -58,7 +58,9 @@
       callback_factory_(this),
       weak_factory_(this) {}
 
-PepperVideoRenderer2D::~PepperVideoRenderer2D() {}
+PepperVideoRenderer2D::~PepperVideoRenderer2D() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
 
 void PepperVideoRenderer2D::SetPepperContext(
     pp::Instance* instance,
diff --git a/remoting/client/software_video_renderer.cc b/remoting/client/software_video_renderer.cc
index 52ff346..11504f66 100644
--- a/remoting/client/software_video_renderer.cc
+++ b/remoting/client/software_video_renderer.cc
@@ -57,6 +57,8 @@
 }
 
 SoftwareVideoRenderer::~SoftwareVideoRenderer() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
   if (decoder_)
     decode_task_runner_->DeleteSoon(FROM_HERE, decoder_.release());
 }
diff --git a/remoting/ios/display/gl_demo_screen.mm b/remoting/ios/display/gl_demo_screen.mm
index c293fe5c..738d1a6 100644
--- a/remoting/ios/display/gl_demo_screen.mm
+++ b/remoting/ios/display/gl_demo_screen.mm
@@ -35,7 +35,9 @@
 // integration. This will draw an expanding checkerboard pattern to the screen.
 GlDemoScreen::GlDemoScreen() : weak_factory_(this)  {}
 
-GlDemoScreen::~GlDemoScreen() {}
+GlDemoScreen::~GlDemoScreen() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
 
 void GlDemoScreen::SetCanvas(base::WeakPtr<Canvas> canvas) {
   canvas_ = canvas;
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 54ba46d..d3a759c7 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3213,21 +3213,6 @@
             ]
         }
     ],
-    "VrShell": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "VrShell"
-                    ]
-                }
-            ]
-        }
-    ],
     "WebApkGooglePlay": [
         {
             "platforms": [
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index cd69f10..9b856ba8 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -42,6 +42,7 @@
 crbug.com/596780 virtual/spv2/compositing/framesets/composited-frame-alignment.html [ Pass ]
 crbug.com/596780 virtual/spv2/compositing/geometry/outline-change.html [ Pass ]
 crbug.com/600618 virtual/spv2/svg/custom/object-current-scale.html [ Pass ]
+crbug.com/682074 virtual/display_list_2d_canvas/fast/canvas/canvas-text-alignment.html [ NeedsRebaseline ]
 
 # Re-add this once it rebaselines.
 # crbug.com/600618 virtual/spv2/svg/custom/object-sizing-explicit-height.xhtml [ Pass ]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/events/relatedTarget-attribute-manual.html b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/events/relatedTarget-attribute-manual.html
new file mode 100644
index 0000000..c5a897d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/events/relatedTarget-attribute-manual.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<html>
+    <head>
+        <title>relatedTarget attribute for dragenter and dragleave events</title>
+        <meta name="viewport" content="width=device-width">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <style>
+        #outerdiv {
+          padding: 50px;
+          background: blue;
+        }
+        #innerdiv {
+          width:200px;
+          height:100px;
+          background: green;
+        }
+        </style>
+        <script>
+            var drag_test = async_test("dragenter and dragleave are correctly fired.");
+            var got_dragenter = false;
+            var got_dragleave = false;
+            function run() {
+                var draggable = document.getElementById("draggable");
+                var innerdiv = document.getElementById("innerdiv");
+                draggable.addEventListener("dragstart", (e) => {
+                    e.dataTransfer.setData("text", draggable.innerHTML);
+                });
+                innerdiv.addEventListener("dragenter", (e) => {
+                    if (!got_dragenter) {
+                        test(function() {
+                            assert_equals(e.relatedTarget.id, "outerdiv", "dragenter event should have the correct relatedTarget.");
+                        }, "dragenter event should have the correct relatedTarget.");
+                        got_dragenter = true;
+                    }
+                });
+                innerdiv.addEventListener("dragleave", (e) => {
+                    if (!got_dragleave) {
+                        test(function() {
+                            assert_equals(e.relatedTarget.id, "outerdiv", "dragleave event should have the correct relatedTarget.");
+                        }, "dragleave event should have the correct relatedTarget.");
+                        got_dragleave = true;
+                        if (got_dragenter)
+                            drag_test.done();
+                    }
+                });
+            }
+        </script>
+    </head>
+    <body onload="run()">
+        <h1>Drag & Drop: relatedTarget attribute for dragenter and dragleave events</h1>
+        <h2 id="pointerTypeDescription"></h2>
+        <h4>Test Description:
+            <ol>
+                 <li>Drag the text into the green box.</li>
+                 <li>Without releasing the drag, drag the text out of the green box.</li>
+            </ol>
+        </h4>
+        <br>
+        <div id="draggable" draggable="true">Drag this text</br>over the green box</div>
+        <div id="outerdiv">
+          <div id="innerdiv"></div>
+        </div>
+    </body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/script-for-event.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/script-for-event.html
new file mode 100644
index 0000000..e3b8e15
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/script-for-event.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<title>Module scripts with for and event attributes</title>
+<link rel="author" title="Matheus Kerschbaum" href="mailto:matjk7@gmail.com">
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var expected = [
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+  true,
+];
+var run = expected.map(function() { return false });
+</script>
+<script for="w&#x130;ndow" event="onload" type="module">
+run[0] = true;
+</script>
+<script for="window" event="onload x" type="module">
+run[1] = true;
+</script>
+<script for="window" event="onload(x" type="module">
+run[2] = true;
+</script>
+<script for="window" event="onload(x)" type="module">
+run[3] = true;
+</script>
+<script for="window" event="onclick" type="module">
+run[4] = true;
+</script>
+<script for="" event="onload" type="module">
+run[5] = true;
+</script>
+<script for="window" event="" type="module">
+run[6] = true;
+</script>
+<script for="" event="" type="module">
+run[7] = true;
+</script>
+<script for="&#xa0;window" event="onload" type="module">
+run[8] = true;
+</script>
+<script for="window&#xa0;" event="onload" type="module">
+run[9] = true;
+</script>
+<script for="window" event="&#xa0;onload" type="module">
+run[10] = true;
+</script>
+<script for="window" event="onload&#xa0;" type="module">
+run[11] = true;
+</script>
+<script for=" window " event=" onload " type="module">
+run[12] = true;
+</script>
+<script for=" window " event=" onload() " type="module">
+run[13] = true;
+</script>
+<script for="object" event="handler" type="module">
+run[14] = true;
+</script>
+<script event="handler" type="module">
+run[15] = true;
+</script>
+<script for="object" type="module">
+run[16] = true;
+</script>
+<script type="module">
+test(function() {
+  for (var i = 0; i < run.length; ++i) {
+    test(function() {
+      var script = document.querySelectorAll("script[for], script[event]")[i];
+      assert_equals(run[i], expected[i],
+                    "script for=" + format_value(script.getAttribute("for")) +
+                    " event=" + format_value(script.getAttribute("event")));
+    }, "Script " + i);
+  }
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt_automation/html/editing/dnd/events/relatedTarget-attribute-manual-automation.js b/third_party/WebKit/LayoutTests/external/wpt_automation/html/editing/dnd/events/relatedTarget-attribute-manual-automation.js
new file mode 100644
index 0000000..4bed7c9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt_automation/html/editing/dnd/events/relatedTarget-attribute-manual-automation.js
@@ -0,0 +1,6 @@
+importAutomationScript('/pointerevents/pointerevent_common_input.js');
+
+function inject_input() {
+    return mouseDragAndDropInTargets(['#draggable', '#outerdiv', '#innerdiv', '#outerdiv']);
+}
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt_automation/pointerevents/pointerevent_common_input.js b/third_party/WebKit/LayoutTests/external/wpt_automation/pointerevents/pointerevent_common_input.js
index b738cb6..d8b682c 100644
--- a/third_party/WebKit/LayoutTests/external/wpt_automation/pointerevents/pointerevent_common_input.js
+++ b/third_party/WebKit/LayoutTests/external/wpt_automation/pointerevents/pointerevent_common_input.js
@@ -440,6 +440,34 @@
   });
 }
 
+// Drag and drop actions
+function mouseDragAndDropInTargets(targetSelectorList) {
+  return new Promise(function(resolve, reject) {
+    if (window.eventSender) {
+      scrollPageIfNeeded(targetSelectorList[0], document);
+      var target = document.querySelector(targetSelectorList[0]);
+      var targetRect = target.getBoundingClientRect();
+      var xPosition = targetRect.left + boundaryOffset;
+      var yPosition = targetRect.top + boundaryOffset;
+      eventSender.mouseMoveTo(xPosition, yPosition);
+      eventSender.mouseDown();
+      eventSender.leapForward(100);
+      for (var i = 1; i < targetSelectorList.length; i++) {
+        scrollPageIfNeeded(targetSelectorList[i], document);
+        target = document.querySelector(targetSelectorList[i]);
+        targetRect = target.getBoundingClientRect();
+        xPosition = targetRect.left + boundaryOffset;
+        yPosition = targetRect.top + boundaryOffset;
+        eventSender.mouseMoveTo(xPosition, yPosition);
+      }
+      eventSender.mouseUp();
+      resolve();
+    } else {
+      reject();
+    }
+  });
+}
+
 // Keyboard inputs.
 function keyboardScroll(direction) {
   return new Promise(function(resolve, reject) {
diff --git a/third_party/WebKit/LayoutTests/resources/testharnessreport.js b/third_party/WebKit/LayoutTests/resources/testharnessreport.js
index 28559fd3..3c4fa649 100644
--- a/third_party/WebKit/LayoutTests/resources/testharnessreport.js
+++ b/third_party/WebKit/LayoutTests/resources/testharnessreport.js
@@ -118,7 +118,8 @@
             src = automationPath + '/fullscreen/auto-click.js';
         } else if (pathAndBase.startsWith('/pointerevents/')
                    || pathAndBase.startsWith('/uievents/')
-                   || pathAndBase.startsWith('/pointerlock/')) {
+                   || pathAndBase.startsWith('/pointerlock/')
+                   || pathAndBase.startsWith('/html/')) {
             // Per-test automation scripts.
             src = automationPath + pathAndBase + '-automation.js';
         } else {
diff --git a/third_party/WebKit/LayoutTests/webaudio/AudioParam/audioparam-nominal-range.html b/third_party/WebKit/LayoutTests/webaudio/AudioParam/audioparam-nominal-range.html
index 47dc585b..edda37e 100644
--- a/third_party/WebKit/LayoutTests/webaudio/AudioParam/audioparam-nominal-range.html
+++ b/third_party/WebKit/LayoutTests/webaudio/AudioParam/audioparam-nominal-range.html
@@ -189,7 +189,7 @@
         args: [1, 1, sampleRate],
       }, {
         creator: "createIIRFilter",
-        args: [[1,2],[3,4]]
+        args: [[1,2],[1, .9]]
       }, {
         creator: "createWaveShaper",
         args: [],
diff --git a/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iir-unstable-expected.txt b/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iir-unstable-expected.txt
new file mode 100644
index 0000000..9def0db
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iir-unstable-expected.txt
@@ -0,0 +1,10 @@
+CONSOLE ERROR: line 39: [audit.js] this test requires the explicit comparison with the expected result when it runs with run-webkit-tests.
+CONSOLE WARNING: line 18: Unstable IIRFilter with feedback coefficients: [1, 1.1]
+This is a testharness.js-based test.
+PASS # AUDIT TASK RUNNER STARTED. 
+PASS > [unstable]  
+PASS   Creating unstable IIR filter with  createIIRFilter([1], [1, 1.1]) did not throw an exception. 
+PASS < [unstable] All assertions passed. (total 1 assertions) 
+PASS # AUDIT TASK RUNNER FINISHED: 1 tasks ran successfully. 
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iir-unstable.html b/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iir-unstable.html
new file mode 100644
index 0000000..11fb1b7c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iir-unstable.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test Unstable IIR Filter</title>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script> 
+    <script src="../resources/audit-util.js"></script>
+    <script src="../resources/audit.js"></script>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner({requireResultFile: true});
+
+      audit.define('unstable', (task, should) => {
+        let context = new OfflineAudioContext(1, 1, 8000);
+        should(() => {
+          context.createIIRFilter([1], [1, 1.1]);
+        }, 'Creating unstable IIR filter with  createIIRFilter([1], [1, 1.1])').notThrow();
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/third_party/WebKit/Source/bindings/core/v8/ScriptPromiseResolver.cpp b/third_party/WebKit/Source/bindings/core/v8/ScriptPromiseResolver.cpp
index fd86dcf..f2bdd5d 100644
--- a/third_party/WebKit/Source/bindings/core/v8/ScriptPromiseResolver.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/ScriptPromiseResolver.cpp
@@ -76,7 +76,7 @@
     if (state_ == kResolving) {
       resolver_.Resolve(value_.NewLocal(script_state_->GetIsolate()));
     } else {
-      ASSERT(state_ == kRejecting);
+      DCHECK_EQ(state_, kRejecting);
       resolver_.Reject(value_.NewLocal(script_state_->GetIsolate()));
     }
   }
diff --git a/third_party/WebKit/Source/bindings/core/v8/ScriptPromiseResolver.h b/third_party/WebKit/Source/bindings/core/v8/ScriptPromiseResolver.h
index 01136b79..8085848d 100644
--- a/third_party/WebKit/Source/bindings/core/v8/ScriptPromiseResolver.h
+++ b/third_party/WebKit/Source/bindings/core/v8/ScriptPromiseResolver.h
@@ -127,7 +127,7 @@
 
     ScriptState::Scope scope(script_state_.Get());
 
-    // Calling ToV8 in a ScriptForbiddenScope will trigger a RELEASE_ASSERT and
+    // Calling ToV8 in a ScriptForbiddenScope will trigger a CHECK and
     // cause a crash. ToV8 just invokes a constructor for wrapper creation,
     // which is safe (no author script can be run). Adding AllowUserAgentScript
     // directly inside createWrapper could cause a perf impact (calling
@@ -145,7 +145,7 @@
       KeepAliveWhilePending();
       return;
     }
-    // TODO(esprehn): This is a hack, instead we should RELEASE_ASSERT that
+    // TODO(esprehn): This is a hack, instead we should CHECK that
     // script is allowed, and v8 should be running the entry hooks below and
     // crashing if script is forbidden. We should then audit all users of
     // ScriptPromiseResolver and the related specs and switch to an async
diff --git a/third_party/WebKit/Source/bindings/core/v8/ScriptValue.cpp b/third_party/WebKit/Source/bindings/core/v8/ScriptValue.cpp
index e7e389e..df7f20c 100644
--- a/third_party/WebKit/Source/bindings/core/v8/ScriptValue.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/ScriptValue.cpp
@@ -47,7 +47,7 @@
   // Probably this could be:
   //   if (&script_state_->world() == &DOMWrapperWorld::current(isolate()))
   //       return v8::Local<v8::Value>();
-  // instead of triggering RELEASE_ASSERT.
+  // instead of triggering CHECK.
   CHECK_EQ(&script_state_->World(), &DOMWrapperWorld::Current(GetIsolate()));
   return value_->NewLocal(GetIsolate());
 }
diff --git a/third_party/WebKit/Source/build/scripts/templates/CSSPropertyMetadata.cpp.tmpl b/third_party/WebKit/Source/build/scripts/templates/CSSPropertyMetadata.cpp.tmpl
index 219fbc6..a89634e9 100644
--- a/third_party/WebKit/Source/build/scripts/templates/CSSPropertyMetadata.cpp.tmpl
+++ b/third_party/WebKit/Source/build/scripts/templates/CSSPropertyMetadata.cpp.tmpl
@@ -65,7 +65,7 @@
 
   if (unresolvedProperty == CSSPropertyVariable)
     return true;
-  ASSERT(unresolvedProperty == CSSPropertyApplyAtRule);
+  DCHECK_EQ(unresolvedProperty, CSSPropertyApplyAtRule);
   return RuntimeEnabledFeatures::cssApplyAtRulesEnabled();
 }
 
diff --git a/third_party/WebKit/Source/build/scripts/templates/MakeQualifiedNames.cpp.tmpl b/third_party/WebKit/Source/build/scripts/templates/MakeQualifiedNames.cpp.tmpl
index 36b0b181..781fe0b 100644
--- a/third_party/WebKit/Source/build/scripts/templates/MakeQualifiedNames.cpp.tmpl
+++ b/third_party/WebKit/Source/build/scripts/templates/MakeQualifiedNames.cpp.tmpl
@@ -97,9 +97,9 @@
     attr_i++;
   }
   {% if tags %}
-  ASSERT(tag_i == {{namespace}}TagsCount);
+  DCHECK_EQ(tag_i, {{namespace}}TagsCount);
   {% endif %}
-  ASSERT(attr_i == {{namespace}}AttrsCount);
+  DCHECK_EQ(attr_i, {{namespace}}AttrsCount);
 }
 
 } // {{namespace}}
diff --git a/third_party/WebKit/Source/core/animation/LegacyStyleInterpolation.cpp b/third_party/WebKit/Source/core/animation/LegacyStyleInterpolation.cpp
index a9b4b53..1190f7d 100644
--- a/third_party/WebKit/Source/core/animation/LegacyStyleInterpolation.cpp
+++ b/third_party/WebKit/Source/core/animation/LegacyStyleInterpolation.cpp
@@ -48,7 +48,7 @@
       cached_fraction_(0),
       cached_iteration_(0),
       cached_value_(start_ ? start_->Clone() : nullptr) {
-  RELEASE_ASSERT(TypesMatch(start_.get(), end_.get()));
+  CHECK(TypesMatch(start_.get(), end_.get()));
 }
 
 void LegacyStyleInterpolation::Apply(StyleResolverState& state) const {
diff --git a/third_party/WebKit/Source/core/clipboard/DataObject.cpp b/third_party/WebKit/Source/core/clipboard/DataObject.cpp
index e21fadf..480e775 100644
--- a/third_party/WebKit/Source/core/clipboard/DataObject.cpp
+++ b/third_party/WebKit/Source/core/clipboard/DataObject.cpp
@@ -248,7 +248,7 @@
 }
 
 bool DataObject::InternalAddStringItem(DataObjectItem* item) {
-  ASSERT(item->Kind() == DataObjectItem::kStringKind);
+  DCHECK_EQ(item->Kind(), DataObjectItem::kStringKind);
   for (size_t i = 0; i < item_list_.size(); ++i) {
     if (item_list_[i]->Kind() == DataObjectItem::kStringKind &&
         item_list_[i]->GetType() == item->GetType())
@@ -260,7 +260,7 @@
 }
 
 void DataObject::InternalAddFileItem(DataObjectItem* item) {
-  ASSERT(item->Kind() == DataObjectItem::kFileKind);
+  DCHECK_EQ(item->Kind(), DataObjectItem::kFileKind);
   item_list_.push_back(item);
 }
 
diff --git a/third_party/WebKit/Source/core/clipboard/DataObjectItem.cpp b/third_party/WebKit/Source/core/clipboard/DataObjectItem.cpp
index 785d4a52..25a6b89 100644
--- a/third_party/WebKit/Source/core/clipboard/DataObjectItem.cpp
+++ b/third_party/WebKit/Source/core/clipboard/DataObjectItem.cpp
@@ -125,7 +125,7 @@
     return nullptr;
   }
 
-  ASSERT(source_ == kPasteboardSource);
+  DCHECK_EQ(source_, kPasteboardSource);
   if (GetType() == kMimeTypeImagePng) {
     WebBlobInfo blob_info = Platform::Current()->Clipboard()->ReadImage(
         WebClipboard::kBufferStandard);
@@ -141,12 +141,12 @@
 }
 
 String DataObjectItem::GetAsString() const {
-  ASSERT(kind_ == kStringKind);
+  DCHECK_EQ(kind_, kStringKind);
 
   if (source_ == kInternalSource)
     return data_;
 
-  ASSERT(source_ == kPasteboardSource);
+  DCHECK_EQ(source_, kPasteboardSource);
 
   WebClipboard::Buffer buffer = Pasteboard::GeneralPasteboard()->GetBuffer();
   String data;
diff --git a/third_party/WebKit/Source/core/dom/ScriptLoader.cpp b/third_party/WebKit/Source/core/dom/ScriptLoader.cpp
index 198e1967..539e130 100644
--- a/third_party/WebKit/Source/core/dom/ScriptLoader.cpp
+++ b/third_party/WebKit/Source/core/dom/ScriptLoader.cpp
@@ -964,8 +964,8 @@
 
   // "If the script element has an event attribute and a for attribute, and
   //  the script's type is "classic", then run these substeps:"
-  // TODO(hiroshige): Check the script's type.
-  if (event_attribute.IsNull() || for_attribute.IsNull())
+  if (GetScriptType() != ScriptType::kClassic || event_attribute.IsNull() ||
+      for_attribute.IsNull())
     return true;
 
   // 3. "Strip leading and trailing ASCII whitespace from event and for."
diff --git a/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.cpp b/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.cpp
index 084583e..59b3511 100644
--- a/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.cpp
+++ b/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.cpp
@@ -22,7 +22,7 @@
                                            int timeout,
                                            bool single_shot) {
   // FIXME: DOMTimers depends heavily on ExecutionContext. Decouple them.
-  ASSERT(context->Timers() == this);
+  DCHECK_EQ(context->Timers(), this);
   int timeout_id = NextID();
   timers_.insert(timeout_id, DOMTimer::Create(context, action, timeout,
                                               single_shot, timeout_id));
diff --git a/third_party/WebKit/Source/core/frame/DeprecatedScheduleStyleRecalcDuringLayout.cpp b/third_party/WebKit/Source/core/frame/DeprecatedScheduleStyleRecalcDuringLayout.cpp
index ca07acf6..ef2ae2f 100644
--- a/third_party/WebKit/Source/core/frame/DeprecatedScheduleStyleRecalcDuringLayout.cpp
+++ b/third_party/WebKit/Source/core/frame/DeprecatedScheduleStyleRecalcDuringLayout.cpp
@@ -22,7 +22,7 @@
   // proper state. The style recalc will still have been schedule, however.
   if (was_in_perform_layout_ &&
       lifecycle_.GetState() != DocumentLifecycle::kInPerformLayout) {
-    ASSERT(lifecycle_.GetState() == DocumentLifecycle::kVisualUpdatePending);
+    DCHECK_EQ(lifecycle_.GetState(), DocumentLifecycle::kVisualUpdatePending);
     lifecycle_.AdvanceTo(DocumentLifecycle::kInPerformLayout);
   }
 }
diff --git a/third_party/WebKit/Source/core/frame/FrameView.cpp b/third_party/WebKit/Source/core/frame/FrameView.cpp
index 8494a86..0335175 100644
--- a/third_party/WebKit/Source/core/frame/FrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/FrameView.cpp
@@ -343,7 +343,7 @@
 }
 
 void FrameView::Dispose() {
-  RELEASE_ASSERT(!IsInPerformLayout());
+  CHECK(!IsInPerformLayout());
 
   if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator())
     scroll_animator->CancelAnimation();
@@ -718,7 +718,7 @@
   if (layout_view_item.IsNull())
     return;
 
-  ASSERT(frame_->View() == this);
+  DCHECK_EQ(frame_->View(), this);
 
   const IntRect rect = layout_view_item.DocumentRect();
   const IntSize& size = rect.Size();
@@ -815,7 +815,7 @@
 
 void FrameView::RecalcOverflowAfterStyleChange() {
   LayoutViewItem layout_view_item = this->GetLayoutViewItem();
-  RELEASE_ASSERT(!layout_view_item.IsNull());
+  CHECK(!layout_view_item.IsNull());
   if (!layout_view_item.NeedsOverflowRecalcAfterStyleChange())
     return;
 
@@ -1158,7 +1158,7 @@
 void FrameView::UpdateLayout() {
   // We should never layout a Document which is not in a LocalFrame.
   DCHECK(frame_);
-  DCHECK(frame_->View() == this);
+  DCHECK_EQ(frame_->View(), this);
   DCHECK(frame_->GetPage());
 
   ScriptForbiddenScope forbid_script;
@@ -1364,7 +1364,7 @@
 
   Lifecycle().AdvanceTo(DocumentLifecycle::kInPaintInvalidation);
 
-  RELEASE_ASSERT(!GetLayoutViewItem().IsNull());
+  CHECK(!GetLayoutViewItem().IsNull());
   LayoutViewItem root_for_paint_invalidation = GetLayoutViewItem();
   DCHECK(!root_for_paint_invalidation.NeedsLayout());
 
@@ -1384,7 +1384,7 @@
 
 void FrameView::InvalidatePaint(
     const PaintInvalidationState& paint_invalidation_state) {
-  RELEASE_ASSERT(!GetLayoutViewItem().IsNull());
+  CHECK(!GetLayoutViewItem().IsNull());
   if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled())
     InvalidatePaintOfScrollControlsIfNeeded(paint_invalidation_state);
 }
diff --git a/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp b/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp
index d9996e7..1e6b1b6 100644
--- a/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp
@@ -347,7 +347,7 @@
 Document* LocalDOMWindow::InstallNewDocument(const String& mime_type,
                                              const DocumentInit& init,
                                              bool force_xhtml) {
-  ASSERT(init.GetFrame() == GetFrame());
+  DCHECK_EQ(init.GetFrame(), GetFrame());
 
   ClearDocument();
 
diff --git a/third_party/WebKit/Source/core/frame/SubresourceIntegrity.cpp b/third_party/WebKit/Source/core/frame/SubresourceIntegrity.cpp
index c96242a..78cc9be4 100644
--- a/third_party/WebKit/Source/core/frame/SubresourceIntegrity.cpp
+++ b/third_party/WebKit/Source/core/frame/SubresourceIntegrity.cpp
@@ -381,7 +381,7 @@
       continue;
     }
 
-    ASSERT(parse_result == kAlgorithmValid);
+    DCHECK_EQ(parse_result, kAlgorithmValid);
 
     if (!ParseDigest(position, current_integrity_end, digest)) {
       error = true;
diff --git a/third_party/WebKit/Source/core/input/EventHandler.cpp b/third_party/WebKit/Source/core/input/EventHandler.cpp
index b370359..6f5bb96 100644
--- a/third_party/WebKit/Source/core/input/EventHandler.cpp
+++ b/third_party/WebKit/Source/core/input/EventHandler.cpp
@@ -1071,7 +1071,8 @@
         mouse_event_manager_->DispatchDragSrcEvent(EventTypeNames::drag, event);
       }
       event_result = mouse_event_manager_->DispatchDragEvent(
-          EventTypeNames::dragenter, new_target, event, data_transfer);
+          EventTypeNames::dragenter, new_target, drag_target_, event,
+          data_transfer);
     }
 
     if (TargetIsFrame(drag_target_.Get(), target_frame)) {
@@ -1079,8 +1080,9 @@
         event_result = target_frame->GetEventHandler().UpdateDragAndDrop(
             event, data_transfer);
     } else if (drag_target_) {
-      mouse_event_manager_->DispatchDragEvent(
-          EventTypeNames::dragleave, drag_target_.Get(), event, data_transfer);
+      mouse_event_manager_->DispatchDragEvent(EventTypeNames::dragleave,
+                                              drag_target_.Get(), new_target,
+                                              event, data_transfer);
     }
 
     if (new_target) {
@@ -1106,7 +1108,7 @@
         mouse_event_manager_->DispatchDragSrcEvent(EventTypeNames::drag, event);
       }
       event_result = mouse_event_manager_->DispatchDragEvent(
-          EventTypeNames::dragover, new_target, event, data_transfer);
+          EventTypeNames::dragover, new_target, nullptr, event, data_transfer);
       should_only_fire_drag_over_event_ = false;
     }
   }
@@ -1124,8 +1126,9 @@
   } else if (drag_target_.Get()) {
     if (mouse_event_manager_->GetDragState().drag_src_)
       mouse_event_manager_->DispatchDragSrcEvent(EventTypeNames::drag, event);
-    mouse_event_manager_->DispatchDragEvent(
-        EventTypeNames::dragleave, drag_target_.Get(), event, data_transfer);
+    mouse_event_manager_->DispatchDragEvent(EventTypeNames::dragleave,
+                                            drag_target_.Get(), nullptr, event,
+                                            data_transfer);
   }
   ClearDragState();
 }
@@ -1141,7 +1144,8 @@
           event, data_transfer);
   } else if (drag_target_.Get()) {
     result = mouse_event_manager_->DispatchDragEvent(
-        EventTypeNames::drop, drag_target_.Get(), event, data_transfer);
+        EventTypeNames::drop, drag_target_.Get(), nullptr, event,
+        data_transfer);
   }
   ClearDragState();
   return result;
diff --git a/third_party/WebKit/Source/core/input/MouseEventManager.cpp b/third_party/WebKit/Source/core/input/MouseEventManager.cpp
index 9667803..addf619 100644
--- a/third_party/WebKit/Source/core/input/MouseEventManager.cpp
+++ b/third_party/WebKit/Source/core/input/MouseEventManager.cpp
@@ -893,21 +893,28 @@
 WebInputEventResult MouseEventManager::DispatchDragSrcEvent(
     const AtomicString& event_type,
     const WebMouseEvent& event) {
-  return DispatchDragEvent(event_type, GetDragState().drag_src_.Get(), event,
-                           GetDragState().drag_data_transfer_.Get());
+  return DispatchDragEvent(event_type, GetDragState().drag_src_.Get(), nullptr,
+                           event, GetDragState().drag_data_transfer_.Get());
 }
 
 WebInputEventResult MouseEventManager::DispatchDragEvent(
     const AtomicString& event_type,
     Node* drag_target,
+    Node* related_target,
     const WebMouseEvent& event,
     DataTransfer* data_transfer) {
   FrameView* view = frame_->View();
-
   // FIXME: We might want to dispatch a dragleave even if the view is gone.
   if (!view)
     return WebInputEventResult::kNotHandled;
 
+  // We should be setting relatedTarget correctly following the spec:
+  // https://html.spec.whatwg.org/multipage/interaction.html#dragevent
+  // At the same time this should prevent exposing a node from another document.
+  if (related_target &&
+      related_target->GetDocument() != drag_target->GetDocument())
+    related_target = nullptr;
+
   const bool cancelable = event_type != EventTypeNames::dragleave &&
                           event_type != EventTypeNames::dragend;
 
@@ -919,7 +926,8 @@
       position.Y(), movement.X(), movement.Y(),
       static_cast<WebInputEvent::Modifiers>(event.GetModifiers()), 0,
       MouseEvent::WebInputEventModifiersToButtons(event.GetModifiers()),
-      nullptr, TimeTicks::FromSeconds(event.TimeStampSeconds()), data_transfer,
+      related_target, TimeTicks::FromSeconds(event.TimeStampSeconds()),
+      data_transfer,
       event.FromTouch() ? MouseEvent::kFromTouch
                         : MouseEvent::kRealOrIndistinguishable);
 
diff --git a/third_party/WebKit/Source/core/input/MouseEventManager.h b/third_party/WebKit/Source/core/input/MouseEventManager.h
index 1da27559..16808a6 100644
--- a/third_party/WebKit/Source/core/input/MouseEventManager.h
+++ b/third_party/WebKit/Source/core/input/MouseEventManager.h
@@ -64,6 +64,7 @@
                                            const WebMouseEvent&);
   WebInputEventResult DispatchDragEvent(const AtomicString& event_type,
                                         Node* target,
+                                        Node* related_target,
                                         const WebMouseEvent&,
                                         DataTransfer*);
 
diff --git a/third_party/WebKit/Source/core/inspector/InspectorAnimationAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorAnimationAgent.cpp
index de6c245..95a1ec7 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorAnimationAgent.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorAnimationAgent.cpp
@@ -387,7 +387,7 @@
     // Refer to CSSAnimations::calculateTransitionUpdateForProperty() for the
     // structure of transitions.
     const KeyframeVector& frames = old_model->GetFrames();
-    ASSERT(frames.size() == 3);
+    DCHECK(frames.size() == 3);
     KeyframeVector new_frames;
     for (int i = 0; i < 3; i++)
       new_frames.push_back(ToTransitionKeyframe(frames[i]->Clone().Get()));
diff --git a/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp
index e1fea63..e9bfe37f 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp
@@ -427,7 +427,7 @@
   }
 
   void Merge(Action* action) override {
-    ASSERT(action->MergeId() == MergeId());
+    DCHECK_EQ(action->MergeId(), MergeId());
 
     SetStyleSheetTextAction* other =
         static_cast<SetStyleSheetTextAction*>(action);
@@ -548,7 +548,7 @@
   bool IsNoop() override { return old_text_ == new_text_; }
 
   void Merge(Action* action) override {
-    ASSERT(action->MergeId() == MergeId());
+    DCHECK_EQ(action->MergeId(), MergeId());
 
     ModifyRuleAction* other = static_cast<ModifyRuleAction*>(action);
     new_text_ = other->new_text_;
@@ -605,7 +605,7 @@
   }
 
   void Merge(Action* action) override {
-    ASSERT(action->MergeId() == MergeId());
+    DCHECK_EQ(action->MergeId(), MergeId());
 
     SetElementStyleAction* other = static_cast<SetElementStyleAction*>(action);
     text_ = other->text_;
diff --git a/third_party/WebKit/Source/core/inspector/MainThreadDebugger.cpp b/third_party/WebKit/Source/core/inspector/MainThreadDebugger.cpp
index 8a7330e..b032703e 100644
--- a/third_party/WebKit/Source/core/inspector/MainThreadDebugger.cpp
+++ b/third_party/WebKit/Source/core/inspector/MainThreadDebugger.cpp
@@ -106,7 +106,7 @@
 
 MainThreadDebugger::~MainThreadDebugger() {
   MutexLocker locker(CreationMutex());
-  ASSERT(instance_ == this);
+  DCHECK_EQ(instance_, this);
   instance_ = nullptr;
 }
 
@@ -242,7 +242,7 @@
   // Do not pause in Context of detached frame.
   if (!paused_frame)
     return;
-  ASSERT(paused_frame == paused_frame->LocalFrameRoot());
+  DCHECK(paused_frame == paused_frame->LocalFrameRoot());
   paused_ = true;
 
   if (UserGestureToken* token = UserGestureIndicator::CurrentToken())
diff --git a/third_party/WebKit/Source/core/layout/LayoutMenuList.cpp b/third_party/WebKit/Source/core/layout/LayoutMenuList.cpp
index 1e210c4..6fc7eaf 100644
--- a/third_party/WebKit/Source/core/layout/LayoutMenuList.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutMenuList.cpp
@@ -125,6 +125,10 @@
     inner_style.SetDirection(option_style_->Direction());
     inner_style.SetUnicodeBidi(option_style_->GetUnicodeBidi());
   }
+
+  // LayoutMenuList::ControlClipRect() depends on inner_block_->ContentsSize().
+  if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled())
+    SetNeedsPaintPropertyUpdate();
 }
 
 HTMLSelectElement* LayoutMenuList::SelectElement() const {
@@ -138,6 +142,10 @@
 
   if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
     cache->ChildrenChanged(this);
+
+  // LayoutMenuList::ControlClipRect() depends on inner_block_->ContentsSize().
+  if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled())
+    SetNeedsPaintPropertyUpdate();
 }
 
 void LayoutMenuList::RemoveChild(LayoutObject* old_child) {
diff --git a/third_party/WebKit/Source/core/loader/HistoryItem.cpp b/third_party/WebKit/Source/core/loader/HistoryItem.cpp
index 3756e2c5..1c0ff7f 100644
--- a/third_party/WebKit/Source/core/loader/HistoryItem.cpp
+++ b/third_party/WebKit/Source/core/loader/HistoryItem.cpp
@@ -72,7 +72,7 @@
 }
 
 void HistoryItem::SetReferrer(const Referrer& referrer) {
-  // This should be a RELEASE_ASSERT.
+  // This should be a CHECK.
   referrer_ = SecurityPolicy::GenerateReferrer(referrer.referrer_policy, Url(),
                                                referrer.referrer);
 }
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
index 42d0cb93..3875ba6f5 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
@@ -791,7 +791,7 @@
   }
 }
 
-static bool NeedsOverflowScroll(const LayoutObject& object) {
+static bool NeedsOverflowClip(const LayoutObject& object) {
   return object.IsBox() && ToLayoutBox(object).ShouldClipOverflow();
 }
 
@@ -800,7 +800,7 @@
     PaintPropertyTreeBuilderFragmentContext& context,
     bool& force_subtree_update) {
   if (object.NeedsPaintPropertyUpdate() || force_subtree_update) {
-    if (NeedsOverflowScroll(object)) {
+    if (NeedsOverflowClip(object)) {
       const LayoutBox& box = ToLayoutBox(object);
       LayoutRect clip_rect;
       clip_rect =
@@ -1186,7 +1186,7 @@
       NeedsPaintOffsetTranslation(object) || NeedsTransform(object) ||
       NeedsEffect(object) || NeedsTransformForNonRootSVG(object) ||
       NeedsFilter(object) || NeedsCssClip(object) ||
-      NeedsScrollbarPaintOffset(object) || NeedsOverflowScroll(object) ||
+      NeedsScrollbarPaintOffset(object) || NeedsOverflowClip(object) ||
       NeedsPerspective(object) || NeedsSVGLocalToBorderBoxTransform(object) ||
       NeedsScrollTranslation(object) || NeedsCssClipFixedPosition(object);
 
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp
index 4269939..71f01e5 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "core/html/HTMLIFrameElement.h"
+#include "core/html/HTMLSelectElement.h"
 #include "core/paint/PaintPropertyTreeBuilderTest.h"
 #include "core/paint/PaintPropertyTreePrinter.h"
 
@@ -752,4 +753,20 @@
   EXPECT_FALSE(transform->FlattensInheritedTransform());
 }
 
+TEST_P(PaintPropertyTreeUpdateTest, MenuListControlClipChange) {
+  SetBodyInnerHTML(
+      "<select id='select' style='white-space: normal'>"
+      "  <option></option>"
+      "  <option>bar</option>"
+      "</select>");
+
+  auto* select = GetLayoutObjectByElementId("select");
+  EXPECT_NE(nullptr, select->PaintProperties()->OverflowClip());
+
+  // Should not assert in FindPropertiesNeedingUpdate.
+  toHTMLSelectElement(select->GetNode())->setSelectedIndex(1);
+  GetDocument().View()->UpdateAllLifecyclePhases();
+  EXPECT_NE(nullptr, select->PaintProperties()->OverflowClip());
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.cpp b/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.cpp
index 346651b..0e4e472d 100644
--- a/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.cpp
+++ b/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.cpp
@@ -309,7 +309,6 @@
   UpdateAuxiliaryObjectProperties(object, context);
 
   if (context.tree_builder_context) {
-    DCHECK(context.tree_builder_context);
     property_tree_builder_.UpdatePropertiesForSelf(
         object, *context.tree_builder_context);
   }
diff --git a/third_party/WebKit/Source/core/workers/WorkerThread.h b/third_party/WebKit/Source/core/workers/WorkerThread.h
index d8da5e4a..1519bb69 100644
--- a/third_party/WebKit/Source/core/workers/WorkerThread.h
+++ b/third_party/WebKit/Source/core/workers/WorkerThread.h
@@ -129,7 +129,7 @@
   bool IsCurrentThread();
 
   WorkerLoaderProxy* GetWorkerLoaderProxy() const {
-    RELEASE_ASSERT(worker_loader_proxy_);
+    CHECK(worker_loader_proxy_);
     return worker_loader_proxy_.Get();
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/TimelineTreeView.js b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineTreeView.js
index ed97566..484b1d1 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline/TimelineTreeView.js
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineTreeView.js
@@ -863,7 +863,7 @@
    */
   _productByEvent(event) {
     var url = TimelineModel.TimelineProfileTree.eventURL(event);
-    if (!url)
+    if (!url || !this._productByURLCache)
       return '';
     if (this._productByURLCache.has(url))
       return this._productByURLCache.get(url);
diff --git a/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2D.cpp b/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2D.cpp
index cc2d4d1..0f4f547 100644
--- a/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2D.cpp
+++ b/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2D.cpp
@@ -853,7 +853,7 @@
   // anti-aliasing, which is expected when !creationAttributes().alpha(), so we
   // need to fall out of display list mode when drawing text to an opaque
   // canvas. crbug.com/583809
-  if (!CreationAttributes().alpha() && !IsComposited()) {
+  if (!IsComposited()) {
     canvas()->DisableDeferral(
         kDisableDeferralReasonSubPixelTextAntiAliasingSupport);
   }
diff --git a/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp b/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp
index 364aac9..5d1cad91 100644
--- a/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp
+++ b/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp
@@ -832,19 +832,6 @@
   EXPECT_FALSE(surface_ptr->IsRecording());
 }
 
-TEST_F(CanvasRenderingContext2DTest,
-       NonOpaqueDisplayListDoesNotFallBackForText) {
-  CreateContext(kNonOpaque);
-  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
-      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
-  auto* surface_ptr = surface.get();
-  CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
-
-  Context2d()->fillText("Text", 0, 5);
-
-  EXPECT_TRUE(surface_ptr->IsRecording());
-}
-
 TEST_F(CanvasRenderingContext2DTest, ImageResourceLifetime) {
   NonThrowableExceptionState non_throwable_exception_state;
   Element* canvas_element =
diff --git a/third_party/WebKit/Source/modules/webaudio/AudioParam.cpp b/third_party/WebKit/Source/modules/webaudio/AudioParam.cpp
index c133ea7..4b89864 100644
--- a/third_party/WebKit/Source/modules/webaudio/AudioParam.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/AudioParam.cpp
@@ -51,7 +51,7 @@
       max_value_(max_value) {
   // The destination MUST exist because we need the destination handler for the
   // AudioParam.
-  RELEASE_ASSERT(context.destination());
+  CHECK(context.destination());
 
   destination_handler_ = &context.destination()->GetAudioDestinationHandler();
   timeline_.SetSmoothedValue(default_value);
diff --git a/third_party/WebKit/Source/modules/webaudio/IIRFilterNode.cpp b/third_party/WebKit/Source/modules/webaudio/IIRFilterNode.cpp
index 42adea1..f8a525d 100644
--- a/third_party/WebKit/Source/modules/webaudio/IIRFilterNode.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/IIRFilterNode.cpp
@@ -7,6 +7,7 @@
 #include "bindings/core/v8/ExceptionMessages.h"
 #include "bindings/core/v8/ExceptionState.h"
 #include "core/dom/ExceptionCode.h"
+#include "core/inspector/ConsoleMessage.h"
 #include "modules/webaudio/AudioBasicProcessorHandler.h"
 #include "modules/webaudio/BaseAudioContext.h"
 #include "modules/webaudio/IIRFilterOptions.h"
@@ -15,6 +16,51 @@
 
 namespace blink {
 
+// Determine if filter is stable based on the feedback coefficients.
+// We compute the reflection coefficients for the filter.  If, at any
+// point, the magnitude of the reflection coefficient is greater than
+// or equal to 1, the filter is declared unstable.
+//
+// Let A(z) be the feedback polynomial given by
+//   A[n](z) = 1 + a[1]/z + a[2]/z^2 + ... + a[n]/z^n
+//
+// The first reflection coefficient k[n] = a[n].  Then, recursively compute
+//
+//   A[n-1](z) = (A[n](z) - k[n]*A[n](1/z)/z^n)/(1-k[n]^2);
+//
+// stopping at A[1](z).  If at any point |k[n]| >= 1, the filter is
+// unstable.
+static bool IsFilterStable(const Vector<double>& feedback_coef) {
+  // Make a copy of the feedback coefficients
+  Vector<double> coef(feedback_coef);
+  int order = coef.size() - 1;
+
+  // If necessary, normalize filter coefficients so that constant term is 1.
+  if (coef[0] != 1) {
+    for (int m = 1; m <= order; ++m)
+      coef[m] /= coef[0];
+    coef[0] = 1;
+  }
+
+  // Begin recursion, using a work array to hold intermediate results.
+  Vector<double> work(order + 1);
+  for (int n = order; n >= 1; --n) {
+    double k = coef[n];
+
+    if (std::fabs(k) >= 1)
+      return false;
+
+    // Note that A[n](1/z)/z^n is basically the coefficients of A[n]
+    // in reverse order.
+    double factor = 1 - k * k;
+    for (int m = 0; m <= n; ++m)
+      work[m] = (coef[m] - k * coef[n - m]) / factor;
+    coef.swap(work);
+  }
+
+  return true;
+}
+
 IIRFilterNode::IIRFilterNode(BaseAudioContext& context,
                              const Vector<double>& feedforward_coef,
                              const Vector<double>& feedback_coef)
@@ -88,6 +134,20 @@
     return nullptr;
   }
 
+  if (!IsFilterStable(feedback_coef)) {
+    StringBuilder message;
+    message.Append("Unstable IIRFilter with feedback coefficients: [");
+    message.AppendNumber(feedback_coef[0]);
+    for (size_t k = 1; k < feedback_coef.size(); ++k) {
+      message.Append(", ");
+      message.AppendNumber(feedback_coef[k]);
+    }
+    message.Append(']');
+
+    context.GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create(
+        kJSMessageSource, kWarningMessageLevel, message.ToString()));
+  }
+
   return new IIRFilterNode(context, feedforward_coef, feedback_coef);
 }
 
diff --git a/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp b/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp
index 58be8223..94a4364 100644
--- a/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp
@@ -166,7 +166,7 @@
 void PannerHandler::ProcessSampleAccurateValues(AudioBus* destination,
                                                 const AudioBus* source,
                                                 size_t frames_to_process) {
-  RELEASE_ASSERT(frames_to_process <= AudioUtilities::kRenderQuantumFrames);
+  CHECK_LE(frames_to_process, AudioUtilities::kRenderQuantumFrames);
 
   // Get the sample accurate values from all of the AudioParams, including the
   // values from the AudioListener.
diff --git a/third_party/WebKit/Source/modules/webdatabase/sqlite/SQLiteDatabase.cpp b/third_party/WebKit/Source/modules/webdatabase/sqlite/SQLiteDatabase.cpp
index 8dd5d0d..ae92d3e 100644
--- a/third_party/WebKit/Source/modules/webdatabase/sqlite/SQLiteDatabase.cpp
+++ b/third_party/WebKit/Source/modules/webdatabase/sqlite/SQLiteDatabase.cpp
@@ -101,7 +101,7 @@
   if (db_) {
     // FIXME: This is being called on the main thread during JS GC.
     // <rdar://problem/5739818>
-    // ASSERT(currentThread() == m_openingThread);
+    // DCHECK_EQ(currentThread(), m_openingThread);
     sqlite3* db = db_;
     {
       MutexLocker locker(database_closing_mutex_);
diff --git a/third_party/WebKit/Source/platform/LifecycleNotifier.h b/third_party/WebKit/Source/platform/LifecycleNotifier.h
index 56ea1799..16e04d3d 100644
--- a/third_party/WebKit/Source/platform/LifecycleNotifier.h
+++ b/third_party/WebKit/Source/platform/LifecycleNotifier.h
@@ -144,7 +144,7 @@
 
 template <typename T, typename Observer>
 inline void LifecycleNotifier<T, Observer>::AddObserver(Observer* observer) {
-  RELEASE_ASSERT(iteration_state_ & kAllowingAddition);
+  CHECK(iteration_state_ & kAllowingAddition);
   observers_.insert(observer);
 }
 
@@ -156,7 +156,7 @@
     observers_.insert(observer);
     return;
   }
-  RELEASE_ASSERT(iteration_state_ & kAllowingRemoval);
+  CHECK(iteration_state_ & kAllowingRemoval);
   observers_.erase(observer);
 }
 
diff --git a/third_party/WebKit/Source/platform/image-decoders/FastSharedBufferReader.cpp b/third_party/WebKit/Source/platform/image-decoders/FastSharedBufferReader.cpp
index eb2f4f9..7d7ce91 100644
--- a/third_party/WebKit/Source/platform/image-decoders/FastSharedBufferReader.cpp
+++ b/third_party/WebKit/Source/platform/image-decoders/FastSharedBufferReader.cpp
@@ -54,7 +54,7 @@
 const char* FastSharedBufferReader::GetConsecutiveData(size_t data_position,
                                                        size_t length,
                                                        char* buffer) const {
-  RELEASE_ASSERT(data_position + length <= data_->size());
+  CHECK_LE(data_position + length, data_->size());
 
   // Use the cached segment if it can serve the request.
   if (data_position >= data_position_ &&
diff --git a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.cpp b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.cpp
index 38cf46e..0d5b949 100644
--- a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.cpp
+++ b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.cpp
@@ -310,8 +310,7 @@
   if (!m_isDefined || !m_table.IsEmpty())
     return;
 
-  RELEASE_ASSERT(m_position + m_colors * BYTES_PER_COLORMAP_ENTRY <=
-                 reader->size());
+  CHECK_LE(m_position + m_colors * BYTES_PER_COLORMAP_ENTRY, reader->size());
   DCHECK_LE(m_colors, MAX_COLORS);
   char buffer[MAX_COLORS * BYTES_PER_COLORMAP_ENTRY];
   const unsigned char* srcColormap =
diff --git a/third_party/WebKit/Source/platform/mhtml/MHTMLParser.cpp b/third_party/WebKit/Source/platform/mhtml/MHTMLParser.cpp
index a4ca390..691d3df 100644
--- a/third_party/WebKit/Source/platform/mhtml/MHTMLParser.cpp
+++ b/third_party/WebKit/Source/platform/mhtml/MHTMLParser.cpp
@@ -303,7 +303,7 @@
       return nullptr;
     }
     end_of_part_reached = true;
-    DCHECK_EQ(next_chars.size(), 2UL);
+    DCHECK(next_chars.size() == 2);
     end_of_archive_reached = (next_chars[0] == '-' && next_chars[1] == '-');
     if (!end_of_archive_reached) {
       String line = line_reader_.NextChunkAsUTF8StringWithLatin1Fallback();
diff --git a/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator.cc b/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator.cc
index 526122d..b3b43cc 100644
--- a/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator.cc
+++ b/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator.cc
@@ -59,9 +59,12 @@
 
 QueueingTimeEstimator::QueueingTimeEstimator(
     QueueingTimeEstimator::Client* client,
-    base::TimeDelta window_duration)
-    : client_(client) {
+    base::TimeDelta window_duration,
+    int steps_per_window)
+    : client_(client), state_(steps_per_window) {
+  DCHECK(steps_per_window >= 1);
   state_.window_duration = window_duration;
+  state_.window_step_width = window_duration / steps_per_window;
 }
 
 QueueingTimeEstimator::QueueingTimeEstimator(const State& state)
@@ -81,6 +84,9 @@
   state_.OnBeginNestedRunLoop();
 }
 
+QueueingTimeEstimator::State::State(int steps_per_window)
+    : step_queueing_times(steps_per_window) {}
+
 void QueueingTimeEstimator::State::OnTopLevelTaskStarted(
     base::TimeTicks task_start_time) {
   current_task_start_time = task_start_time;
@@ -94,10 +100,8 @@
     current_task_start_time = base::TimeTicks();
     return;
   }
-
   if (window_start_time.is_null())
     window_start_time = current_task_start_time;
-
   if (task_end_time - current_task_start_time > kInvalidTaskThreshold) {
     // This task took too long, so we'll pretend it never happened. This could
     // be because the user's machine went to sleep during a task.
@@ -108,18 +112,20 @@
   while (TimePastWindowEnd(task_end_time)) {
     if (!TimePastWindowEnd(current_task_start_time)) {
       // Include the current task in this window.
-      current_expected_queueing_time += ExpectedQueueingTimeFromTask(
+      step_expected_queueing_time += ExpectedQueueingTimeFromTask(
           current_task_start_time, task_end_time, window_start_time,
-          window_start_time + window_duration);
+          window_start_time + window_step_width);
     }
-    client->OnQueueingTimeForWindowEstimated(current_expected_queueing_time);
-    window_start_time += window_duration;
-    current_expected_queueing_time = base::TimeDelta();
+    step_queueing_times.Add(step_expected_queueing_time);
+    client->OnQueueingTimeForWindowEstimated(step_queueing_times.GetAverage(),
+                                             window_start_time);
+    window_start_time += window_step_width;
+    step_expected_queueing_time = base::TimeDelta();
   }
 
-  current_expected_queueing_time += ExpectedQueueingTimeFromTask(
+  step_expected_queueing_time += ExpectedQueueingTimeFromTask(
       current_task_start_time, task_end_time, window_start_time,
-      window_start_time + window_duration);
+      window_start_time + window_step_width);
 
   current_task_start_time = base::TimeTicks();
 }
@@ -129,7 +135,27 @@
 }
 
 bool QueueingTimeEstimator::State::TimePastWindowEnd(base::TimeTicks time) {
-  return time > window_start_time + window_duration;
+  return time > window_start_time + window_step_width;
+}
+
+QueueingTimeEstimator::RunningAverage::RunningAverage(int size) {
+  circular_buffer_.resize(size);
+  index_ = 0;
+}
+
+int QueueingTimeEstimator::RunningAverage::GetStepsPerWindow() const {
+  return circular_buffer_.size();
+}
+
+void QueueingTimeEstimator::RunningAverage::Add(base::TimeDelta bin_value) {
+  running_sum_ -= circular_buffer_[index_];
+  circular_buffer_[index_] = bin_value;
+  running_sum_ += bin_value;
+  index_ = (index_ + 1) % circular_buffer_.size();
+}
+
+base::TimeDelta QueueingTimeEstimator::RunningAverage::GetAverage() const {
+  return running_sum_ / circular_buffer_.size();
 }
 
 // Keeps track of the queueing time.
@@ -137,7 +163,8 @@
  public:
   // QueueingTimeEstimator::Client implementation:
   void OnQueueingTimeForWindowEstimated(
-      base::TimeDelta queueing_time) override {
+      base::TimeDelta queueing_time,
+      base::TimeTicks window_start_time) override {
     queueing_time_ = queueing_time;
   }
 
@@ -167,11 +194,26 @@
   temporary_queueing_time_estimator_state.OnTopLevelTaskCompleted(
       &record_queueing_time_client, now);
 
-  // Report the max of the queueing time for the last full window, or the
-  // current partial window.
-  return std::max(
-      record_queueing_time_client.queueing_time(),
-      temporary_queueing_time_estimator_state.current_expected_queueing_time);
+  // Report the max of the queueing time for the last window, or the on-going
+  // window (tmp window in chart) which includes the current task.
+  //
+  //                                Estimate
+  //                                  |
+  //                                  v
+  // Actual Task     |-------------------------...
+  // Assumed Task    |----------------|
+  // Time       |---o---o---o---o---o---o-------->
+  //            0   1   2   3   4   5   6
+  //            | s | s | s | s | s | s |
+  //            |----last window----|
+  //                |----tmp window-----|
+  RunningAverage& temporary_step_queueing_times =
+      temporary_queueing_time_estimator_state.step_queueing_times;
+  temporary_step_queueing_times.Add(
+      temporary_queueing_time_estimator_state.step_expected_queueing_time);
+
+  return std::max(record_queueing_time_client.queueing_time(),
+                  temporary_step_queueing_times.GetAverage());
 }
 
 }  // namespace scheduler
diff --git a/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator.h b/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator.h
index aea0e6b..e548cb9 100644
--- a/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator.h
+++ b/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator.h
@@ -9,6 +9,8 @@
 #include "base/time/time.h"
 #include "platform/PlatformExport.h"
 
+#include <vector>
+
 namespace blink {
 namespace scheduler {
 
@@ -19,7 +21,8 @@
   class PLATFORM_EXPORT Client {
    public:
     virtual void OnQueueingTimeForWindowEstimated(
-        base::TimeDelta queueing_time) = 0;
+        base::TimeDelta queueing_time,
+        base::TimeTicks window_start_time) = 0;
     Client() {}
     virtual ~Client() {}
 
@@ -27,23 +30,71 @@
     DISALLOW_COPY_AND_ASSIGN(Client);
   };
 
+  class RunningAverage {
+   public:
+    explicit RunningAverage(int steps_per_window);
+    int GetStepsPerWindow() const;
+    void Add(base::TimeDelta bin_value);
+    base::TimeDelta GetAverage() const;
+
+   private:
+    int index_;
+    std::vector<base::TimeDelta> circular_buffer_;
+    base::TimeDelta running_sum_;
+  };
+
   class State {
    public:
+    explicit State(int steps_per_window);
     void OnTopLevelTaskStarted(base::TimeTicks task_start_time);
     void OnTopLevelTaskCompleted(Client* client, base::TimeTicks task_end_time);
     void OnBeginNestedRunLoop();
 
-    base::TimeDelta current_expected_queueing_time;
+    // |step_expected_queueing_time| is the expected queuing time of a
+    // smaller window of a step's width. By combining these step EQTs through a
+    // running average, we can get window EQTs of a bigger window.
+    //
+    // ^ Instantaneous queuing time
+    // |
+    // |
+    // |   |\                                           .
+    // |   | \            |\             |\             .
+    // |   |  \           | \       |\   | \            .
+    // |   |   \    |\    |  \      | \  |  \           .
+    // |   |    \   | \   |   \     |  \ |   \          .
+    // ------------------------------------------------> Time
+    //
+    // |stepEQT|stepEQT|stepEQT|stepEQT|stepEQT|stepEQT|
+    //
+    // |------windowEQT_1------|
+    //         |------windowEQT_2------|
+    //                 |------windowEQT_3------|
+    //
+    // In this case:
+    // |steps_per_window| = 3, because each window is the length of 3 steps.
+
+    base::TimeDelta step_expected_queueing_time;
+    // |window_duration| is the size of the sliding window.
     base::TimeDelta window_duration;
+    base::TimeDelta window_step_width;
+    // |steps_per_window| is the ratio of |window_duration| to the sliding
+    // window's step width. It is an integer since the window must be a integer
+    // multiple of the step's width. This parameter is used for deciding the
+    // sliding window's step width, and the number of bins of the circular
+    // buffer.
+    int steps_per_window;
     base::TimeTicks window_start_time;
     base::TimeTicks current_task_start_time;
+    RunningAverage step_queueing_times;
 
    private:
     bool TimePastWindowEnd(base::TimeTicks task_end_time);
     bool in_nested_message_loop_ = false;
   };
 
-  QueueingTimeEstimator(Client* client, base::TimeDelta window_duration);
+  QueueingTimeEstimator(Client* client,
+                        base::TimeDelta window_duration,
+                        int steps_per_window);
   explicit QueueingTimeEstimator(const State& state);
 
   void OnTopLevelTaskStarted(base::TimeTicks task_start_time);
diff --git a/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator_unittest.cc b/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator_unittest.cc
index f1526c6d..1a5fa904 100644
--- a/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator_unittest.cc
+++ b/third_party/WebKit/Source/platform/scheduler/base/queueing_time_estimator_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "platform/scheduler/base/queueing_time_estimator.h"
+#include "base/logging.h"
 #include "platform/scheduler/base/test_time_source.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -15,7 +16,8 @@
 class TestQueueingTimeEstimatorClient : public QueueingTimeEstimator::Client {
  public:
   void OnQueueingTimeForWindowEstimated(
-      base::TimeDelta queueing_time) override {
+      base::TimeDelta queueing_time,
+      base::TimeTicks window_start_time) override {
     expected_queueing_times_.push_back(queueing_time);
   }
   const std::vector<base::TimeDelta>& expected_queueing_times() {
@@ -29,8 +31,9 @@
 class QueueingTimeEstimatorForTest : public QueueingTimeEstimator {
  public:
   QueueingTimeEstimatorForTest(TestQueueingTimeEstimatorClient* client,
-                               base::TimeDelta window_duration)
-      : QueueingTimeEstimator(client, window_duration) {}
+                               base::TimeDelta window_duration,
+                               int steps_per_window)
+      : QueueingTimeEstimator(client, window_duration, steps_per_window) {}
 };
 
 // Three tasks of one second each, all within a 5 second window. Expected
@@ -41,7 +44,7 @@
   base::TimeTicks time;
   TestQueueingTimeEstimatorClient client;
   QueueingTimeEstimatorForTest estimator(&client,
-                                         base::TimeDelta::FromSeconds(5));
+                                         base::TimeDelta::FromSeconds(5), 1);
   for (int i = 0; i < 3; ++i) {
     estimator.OnTopLevelTaskStarted(time);
     time += base::TimeDelta::FromMilliseconds(1000);
@@ -68,7 +71,7 @@
 TEST_F(QueueingTimeEstimatorTest, MultiWindowTask) {
   TestQueueingTimeEstimatorClient client;
   QueueingTimeEstimatorForTest estimator(&client,
-                                         base::TimeDelta::FromSeconds(5));
+                                         base::TimeDelta::FromSeconds(5), 1);
   base::TimeTicks time;
   time += base::TimeDelta::FromMilliseconds(5000);
   estimator.OnTopLevelTaskStarted(time);
@@ -103,7 +106,7 @@
        EstimateQueueingTimeDuringSingleLongTaskIncompleteWindow) {
   TestQueueingTimeEstimatorClient client;
   QueueingTimeEstimatorForTest estimator(&client,
-                                         base::TimeDelta::FromSeconds(5));
+                                         base::TimeDelta::FromSeconds(5), 1);
   base::TimeTicks time;
   time += base::TimeDelta::FromMilliseconds(5000);
   estimator.OnTopLevelTaskStarted(time);
@@ -128,7 +131,7 @@
        EstimateQueueingTimeDuringSingleLongTaskExceedingWindow) {
   TestQueueingTimeEstimatorClient client;
   QueueingTimeEstimatorForTest estimator(&client,
-                                         base::TimeDelta::FromSeconds(5));
+                                         base::TimeDelta::FromSeconds(5), 1);
   base::TimeTicks time;
   time += base::TimeDelta::FromMilliseconds(5000);
   estimator.OnTopLevelTaskStarted(time);
@@ -144,13 +147,87 @@
 
   EXPECT_EQ(base::TimeDelta::FromMilliseconds(5500), estimated_queueing_time);
 }
+//                         Estimate
+//                           |
+//                           v
+// Task|------------------------------...
+// Time|---o---o---o---o---o---o-------->
+//     0   1   2   3   4   5   6
+//     | s | s | s | s | s |
+//     |--------win1-------|
+//         |--------win2-------|
+//
+// s: step window
+// win1: The last full window.
+// win2: The partial window.
+//
+// EQT(win1) = (0.5 + 5.5) / 2 * (5 / 5) = 3
+// EQT(win2) = (4.5 + 0) / 2 * (4.5 / 5) = 2.025
+// So EQT = max(EQT(win1), EQT(win2)) = 3
+TEST_F(QueueingTimeEstimatorTest,
+       SlidingWindowEstimateQueueingTimeFullWindowLargerThanPartial) {
+  TestQueueingTimeEstimatorClient client;
+  QueueingTimeEstimatorForTest estimator(&client,
+                                         base::TimeDelta::FromSeconds(5), 5);
+  base::TimeTicks time;
+  time += base::TimeDelta::FromMilliseconds(5000);
+  estimator.OnTopLevelTaskStarted(time);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  base::TimeTicks start_time = time;
+  estimator.OnTopLevelTaskStarted(start_time);
+
+  time += base::TimeDelta::FromMilliseconds(5500);
+
+  base::TimeDelta estimated_queueing_time =
+      estimator.EstimateQueueingTimeIncludingCurrentTask(time);
+
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(3000), estimated_queueing_time);
+}
+//                        Estimate
+//                           |
+//                           v
+// Task                    |----------...
+// Time|---o---o---o---o---o---o-------->
+//     0   1   2   3   4   5   6
+//     | s | s | s | s | s |
+//     |--------win1-------|
+//         |--------win2-------|
+//
+// s: step window
+// win1: The last full window.
+// win2: The partial window.
+//
+// EQT(win1) = 0
+// EQT(win2) = (0 + 0.5) / 2 * (0.5 / 2) = 0.025
+// So EQT = max(EQT(win1), EQT(win2)) = 0.025
+TEST_F(QueueingTimeEstimatorTest,
+       SlidingWindowEstimateQueueingTimePartialWindowLargerThanFull) {
+  TestQueueingTimeEstimatorClient client;
+  QueueingTimeEstimatorForTest estimator(&client,
+                                         base::TimeDelta::FromSeconds(5), 5);
+  base::TimeTicks time;
+  time += base::TimeDelta::FromMilliseconds(5000);
+  estimator.OnTopLevelTaskStarted(time);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  time += base::TimeDelta::FromMilliseconds(5000);
+  base::TimeTicks start_time = time;
+  estimator.OnTopLevelTaskStarted(start_time);
+  time += base::TimeDelta::FromMilliseconds(500);
+
+  base::TimeDelta estimated_queueing_time =
+      estimator.EstimateQueueingTimeIncludingCurrentTask(time);
+
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(25), estimated_queueing_time);
+}
 
 // Tasks containing nested run loops may be extremely long without
 // negatively impacting user experience. Ignore such tasks.
 TEST_F(QueueingTimeEstimatorTest, IgnoresTasksWithNestedMessageLoops) {
   TestQueueingTimeEstimatorClient client;
   QueueingTimeEstimatorForTest estimator(&client,
-                                         base::TimeDelta::FromSeconds(5));
+                                         base::TimeDelta::FromSeconds(5), 1);
   base::TimeTicks time;
   time += base::TimeDelta::FromMilliseconds(5000);
   estimator.OnTopLevelTaskStarted(time);
@@ -189,7 +266,7 @@
 TEST_F(QueueingTimeEstimatorTest, IgnoreExtremelyLongTasks) {
   TestQueueingTimeEstimatorClient client;
   QueueingTimeEstimatorForTest estimator(&client,
-                                         base::TimeDelta::FromSeconds(5));
+                                         base::TimeDelta::FromSeconds(5), 1);
   // Start with a 1 second task.
   base::TimeTicks time;
   estimator.OnTopLevelTaskStarted(time);
@@ -223,5 +300,153 @@
                                    base::TimeDelta::FromMilliseconds(100)));
 }
 
+// ^ Instantaneous queuing time
+// |
+// |
+// |   |\                                          .
+// |   |  \                                        .
+// |   |    \                                      .
+// |   |      \                                    .
+// |   |        \             |                    .
+// ------------------------------------------------> Time
+//     |s|s|s|s|s|
+//     |---win---|
+//       |---win---|
+//         |---win---|
+TEST_F(QueueingTimeEstimatorTest, SlidingWindowOverOneTask) {
+  TestQueueingTimeEstimatorClient client;
+  QueueingTimeEstimatorForTest estimator(&client,
+                                         base::TimeDelta::FromSeconds(5), 5);
+  base::TimeTicks time;
+  time += base::TimeDelta::FromMilliseconds(1000);
+
+  estimator.OnTopLevelTaskStarted(time);
+  time += base::TimeDelta::FromMilliseconds(5000);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  time += base::TimeDelta::FromMilliseconds(6000);
+
+  estimator.OnTopLevelTaskStarted(time);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  EXPECT_THAT(client.expected_queueing_times(),
+              testing::ElementsAre(base::TimeDelta::FromMilliseconds(900),
+                                   base::TimeDelta::FromMilliseconds(1600),
+                                   base::TimeDelta::FromMilliseconds(2100),
+                                   base::TimeDelta::FromMilliseconds(2400),
+                                   base::TimeDelta::FromMilliseconds(2500),
+                                   base::TimeDelta::FromMilliseconds(1600),
+                                   base::TimeDelta::FromMilliseconds(900),
+                                   base::TimeDelta::FromMilliseconds(400),
+                                   base::TimeDelta::FromMilliseconds(100),
+                                   base::TimeDelta::FromMilliseconds(0)));
+}
+
+// ^ Instantaneous queuing time
+// |
+// |
+// |   |\                                            .
+// |   | \                                           .
+// |   |  \                                          .
+// |   |   \  |\                                     .
+// |   |    \ | \           |                        .
+// ------------------------------------------------> Time
+//     |s|s|s|s|s|
+//     |---win---|
+//       |---win---|
+//         |---win---|
+TEST_F(QueueingTimeEstimatorTest, SlidingWindowOverTwoTasksWithinFirstWindow) {
+  TestQueueingTimeEstimatorClient client;
+  QueueingTimeEstimatorForTest estimator(&client,
+                                         base::TimeDelta::FromSeconds(5), 5);
+  base::TimeTicks time;
+  time += base::TimeDelta::FromMilliseconds(1000);
+
+  estimator.OnTopLevelTaskStarted(time);
+  time += base::TimeDelta::FromMilliseconds(2500);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  time += base::TimeDelta::FromMilliseconds(500);
+
+  estimator.OnTopLevelTaskStarted(time);
+  time += base::TimeDelta::FromMilliseconds(1000);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  time += base::TimeDelta::FromMilliseconds(6000);
+
+  estimator.OnTopLevelTaskStarted(time);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  std::vector<base::TimeDelta> expected_durations = {
+      base::TimeDelta::FromMilliseconds(400),
+      base::TimeDelta::FromMilliseconds(600),
+      base::TimeDelta::FromMilliseconds(625),
+      base::TimeDelta::FromMilliseconds(725),
+      base::TimeDelta::FromMilliseconds(725),
+      base::TimeDelta::FromMilliseconds(325),
+      base::TimeDelta::FromMilliseconds(125),
+      base::TimeDelta::FromMilliseconds(100),
+      base::TimeDelta::FromMilliseconds(0)};
+  EXPECT_THAT(client.expected_queueing_times(),
+              testing::ElementsAreArray(expected_durations));
+}
+
+// ^ Instantaneous queuing time
+// |
+// |
+// |           |\                                 .
+// |           | \                                .
+// |           |  \                               .
+// |           |   \ |\                           .
+// |           |    \| \           |              .
+// ------------------------------------------------> Time
+//     |s|s|s|s|s|
+//     |---win---|
+//       |---win---|
+//         |---win---|
+TEST_F(QueueingTimeEstimatorTest,
+       SlidingWindowOverTwoTasksSpanningSeveralWindows) {
+  TestQueueingTimeEstimatorClient client;
+  QueueingTimeEstimatorForTest estimator(&client,
+                                         base::TimeDelta::FromSeconds(5), 5);
+  base::TimeTicks time;
+  time += base::TimeDelta::FromMilliseconds(1000);
+  estimator.OnTopLevelTaskStarted(time);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  time += base::TimeDelta::FromMilliseconds(4000);
+
+  estimator.OnTopLevelTaskStarted(time);
+  time += base::TimeDelta::FromMilliseconds(2500);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  estimator.OnTopLevelTaskStarted(time);
+  time += base::TimeDelta::FromMilliseconds(1000);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  time += base::TimeDelta::FromMilliseconds(6000);
+
+  estimator.OnTopLevelTaskStarted(time);
+  estimator.OnTopLevelTaskCompleted(time);
+
+  std::vector<base::TimeDelta> expected_durations = {
+      base::TimeDelta::FromMilliseconds(0),
+      base::TimeDelta::FromMilliseconds(0),
+      base::TimeDelta::FromMilliseconds(0),
+      base::TimeDelta::FromMilliseconds(0),
+      base::TimeDelta::FromMilliseconds(400),
+      base::TimeDelta::FromMilliseconds(600),
+      base::TimeDelta::FromMilliseconds(700),
+      base::TimeDelta::FromMilliseconds(725),
+      base::TimeDelta::FromMilliseconds(725),
+      base::TimeDelta::FromMilliseconds(325),
+      base::TimeDelta::FromMilliseconds(125),
+      base::TimeDelta::FromMilliseconds(25),
+      base::TimeDelta::FromMilliseconds(0)};
+
+  EXPECT_THAT(client.expected_queueing_times(),
+              testing::ElementsAreArray(expected_durations));
+}
+
 }  // namespace scheduler
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
index 09f5c3dd..0a0d3ced 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
@@ -50,6 +50,8 @@
 // We do not throttle anything while audio is played and shortly after that.
 constexpr base::TimeDelta kThrottlingDelayAfterAudioIsPlayed =
     base::TimeDelta::FromSeconds(5);
+constexpr base::TimeDelta kQueueingTimeWindowDuration =
+    base::TimeDelta::FromSeconds(1);
 
 void ReportForegroundRendererTaskLoad(base::TimeTicks time, double load) {
   if (!blink::RuntimeEnabledFeatures::timerThrottlingForBackgroundTabsEnabled())
@@ -100,7 +102,7 @@
                      base::Unretained(this)),
           helper_.ControlTaskQueue()),
       seqlock_queueing_time_estimator_(
-          QueueingTimeEstimator(this, base::TimeDelta::FromSeconds(1))),
+          QueueingTimeEstimator(this, kQueueingTimeWindowDuration, 20)),
       main_thread_only_(this,
                         compositor_task_queue_,
                         helper_.scheduler_tqm_delegate().get(),
@@ -1694,7 +1696,6 @@
   base::TimeDelta estimated_queueing_time;
 
   bool can_read = false;
-  QueueingTimeEstimator::State queueing_time_estimator_state;
 
   base::subtle::Atomic32 version;
   seqlock_queueing_time_estimator_.seqlock.TryRead(&can_read, &version);
@@ -1704,7 +1705,7 @@
   if (!can_read)
     return GetCompositorThreadOnly().main_thread_seems_unresponsive;
 
-  queueing_time_estimator_state =
+  QueueingTimeEstimator::State queueing_time_estimator_state =
       seqlock_queueing_time_estimator_.data.GetState();
 
   // If we fail to determine if the main thread is busy, assume whether or not
@@ -1855,12 +1856,26 @@
 }
 
 void RendererSchedulerImpl::OnQueueingTimeForWindowEstimated(
-    base::TimeDelta queueing_time) {
+    base::TimeDelta queueing_time,
+    base::TimeTicks window_start_time) {
+  // RendererScheduler reports the queueing time once per window's duration.
+  //          |stepEQT|stepEQT|stepEQT|stepEQT|stepEQT|stepEQT|
+  // Report:  |-------window EQT------|
+  // Discard:         |-------window EQT------|
+  // Discard:                 |-------window EQT------|
+  // Report:                          |-------window EQT------|
+  if (window_start_time -
+          GetMainThreadOnly().uma_last_queueing_time_report_window_start_time <
+      kQueueingTimeWindowDuration) {
+    return;
+  }
   UMA_HISTOGRAM_TIMES("RendererScheduler.ExpectedTaskQueueingDuration",
                       queueing_time);
   TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
                  "estimated_queueing_time_for_window",
                  queueing_time.InMillisecondsF());
+  GetMainThreadOnly().uma_last_queueing_time_report_window_start_time =
+      window_start_time;
 }
 
 AutoAdvancingVirtualTimeDomain* RendererSchedulerImpl::GetVirtualTimeDomain() {
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
index 0f3570db..bc13c3f 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
@@ -140,7 +140,9 @@
   void OnBeginNestedRunLoop() override;
 
   // QueueingTimeEstimator::Client implementation:
-  void OnQueueingTimeForWindowEstimated(base::TimeDelta queueing_time) override;
+  void OnQueueingTimeForWindowEstimated(
+      base::TimeDelta queueing_time,
+      base::TimeTicks window_start_time) override;
 
   scoped_refptr<TaskQueue> DefaultTaskQueue();
   scoped_refptr<TaskQueue> CompositorTaskQueue();
@@ -458,6 +460,7 @@
     base::TimeTicks current_policy_expiration_time;
     base::TimeTicks estimated_next_frame_begin;
     base::TimeTicks current_task_start_time;
+    base::TimeTicks uma_last_queueing_time_report_window_start_time;
     base::TimeDelta compositor_frame_interval;
     base::TimeDelta longest_jank_free_task_duration;
     base::Optional<base::TimeTicks> last_audio_state_change;
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
index 5645568..4e00051 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
@@ -3826,6 +3826,32 @@
       scheduler_->MainThreadSeemsUnresponsive(responsiveness_threshold()));
 }
 
+// As |responsiveness_threshold| == expected queueing time threshold == 0.2s,
+// for a task shorter than the length of the window (1s), the critical value of
+// the length of task x can be calculated by (x/2) * (x/1) = 0.2, in which x =
+// 0.6324.
+TEST_F(RendererSchedulerImplTest, UnresponsiveMainThreadAboveThreshold) {
+  EXPECT_FALSE(
+      scheduler_->MainThreadSeemsUnresponsive(responsiveness_threshold()));
+
+  AdvanceTimeWithTask(0.64);
+  EXPECT_TRUE(
+      scheduler_->MainThreadSeemsUnresponsive(responsiveness_threshold()));
+}
+
+// As |responsiveness_threshold| == expected queueing time threshold == 0.2s,
+// for a task shorter than the length of the window (1s), the critical value of
+// the length of task x can be calculated by (x/2) * (x/1) = 0.2, in which x =
+// 0.6324.
+TEST_F(RendererSchedulerImplTest, ResponsiveMainThreadBelowThreshold) {
+  EXPECT_FALSE(
+      scheduler_->MainThreadSeemsUnresponsive(responsiveness_threshold()));
+
+  AdvanceTimeWithTask(0.63);
+  EXPECT_FALSE(
+      scheduler_->MainThreadSeemsUnresponsive(responsiveness_threshold()));
+}
+
 TEST_F(RendererSchedulerImplTest, ResponsiveMainThreadDuringTask) {
   EXPECT_FALSE(
       scheduler_->MainThreadSeemsUnresponsive(responsiveness_threshold()));
diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.cpp b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.cpp
index 635b8ef2..8d843bf5 100644
--- a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.cpp
+++ b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.cpp
@@ -374,8 +374,8 @@
   // Changing suborigins midstream is bad. Very bad. It should not happen.
   // This is, in fact,  one of the very basic invariants that makes
   // suborigins an effective security tool.
-  RELEASE_ASSERT(suborigin_.GetName().IsNull() ||
-                 (suborigin_.GetName() == suborigin.GetName()));
+  CHECK(suborigin_.GetName().IsNull() ||
+        (suborigin_.GetName() == suborigin.GetName()));
   suborigin_.SetTo(suborigin);
 }
 
diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h
index d346c9f..4aa9292 100644
--- a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h
+++ b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h
@@ -202,7 +202,7 @@
   // Assigns a suborigin namespace to the SecurityOrigin. addSuborigin() must
   // only ever be called once per SecurityOrigin(). If it is called on a
   // SecurityOrigin that has already had a suborigin assigned, it will hit a
-  // RELEASE_ASSERT().
+  // CHECK().
   bool HasSuborigin() const { return !suborigin_.GetName().IsNull(); }
   const Suborigin* GetSuborigin() const { return &suborigin_; }
   void AddSuborigin(const Suborigin&);
diff --git a/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp b/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp
index cddba83c..1873df9 100644
--- a/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp
+++ b/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp
@@ -1954,7 +1954,8 @@
 }
 
 void WebLocalFrameImpl::SetCanHaveScrollbars(bool can_have_scrollbars) {
-  GetFrame()->View()->SetCanHaveScrollbars(can_have_scrollbars);
+  if (FrameView* view = GetFrameView())
+    view->SetCanHaveScrollbars(can_have_scrollbars);
 }
 
 void WebLocalFrameImpl::SetInputEventsTransformForEmulation(
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 57e79d71..a17e5f4 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -438,26 +438,6 @@
       'Windows Dev': 'debug_bot',
     },
 
-    # TODO(mmoss): Move these to mb_configs_official.pyl in the release repo
-    # once the continuous builders are migrated to use recipes from that repo.
-    'official.desktop.continuous': {
-      'mac beta': 'official',
-      'mac stable': 'official',
-      'mac trunk': 'official',
-      'linux64 beta': 'official_six_concurrent_links',
-      'linux64 stable': 'official_six_concurrent_links',
-      'linux64 trunk': 'official_six_concurrent_links',
-      # TODO(mmoss): These precise64 entries can go away once the newly renamed
-      # builders are fully deployed.
-      'precise64 beta': 'official_six_concurrent_links',
-      'precise64 stable': 'official_six_concurrent_links',
-      'precise64 trunk': 'official_six_concurrent_links',
-      'win beta': 'official_six_concurrent_links',
-      'win stable': 'official_six_concurrent_links',
-      'win trunk': 'official_six_concurrent_links',
-      'win64 trunk': 'official_six_concurrent_links',
-    },
-
     'tryserver.blink': {
       # Most tryservers should have '_trybot' in their config names, but
       # 'release_trybot' includes 'dcheck_always_on', and the blink
@@ -1323,10 +1303,6 @@
       'msan', 'release_bot',
     ],
 
-    'official': [
-      'official',
-    ],
-
     'official_goma_minimal_symbols_clang': [
       'official', 'goma', 'minimal_symbols', 'clang',
     ],
@@ -1371,10 +1347,6 @@
       'official_optimize', 'chrome_pgo_phase_2', 'x86',
     ],
 
-    'official_six_concurrent_links': [
-      'official', 'six_concurrent_links',
-    ],
-
     'release_bot_ozone_linux': [
       'release_bot', 'ozone', 'ozone_linux',
     ],
@@ -1854,12 +1826,6 @@
       'mixins': ['shared', 'release', 'goma']
     },
 
-    'six_concurrent_links': {
-      # TODO(crbug.com/611491) Adjust the get_concurrent_links script
-      # to be more conservative so that we don't need this.
-      'gn_args': 'concurrent_links=6',
-    },
-
     'static': {
       'gn_args': 'is_component_build=false',
     },
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 441ddd975..3862a2a 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -642,6 +642,14 @@
   </summary>
 </histogram>
 
+<histogram name="Android.DownloadManager.ShowStorageInfo" enum="BooleanVisible">
+  <owner>shaktisahu@chromium.org</owner>
+  <summary>
+    Recorded when the user clicks the info button on download home to toggle the
+    storage info. The state recorded is after the visibility is toggled.
+  </summary>
+</histogram>
+
 <histogram name="Android.DownloadManager.SpaceUsed" units="%">
   <owner>dfalcantara@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
@@ -2588,21 +2596,21 @@
 </histogram>
 
 <histogram name="AsyncDNS.AttemptCountFail">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Count of DnsAttempts before DnsTransaction completes with failure.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.AttemptCountSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Count of DnsAttempts before DnsTransaction completes successfully.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.ConfigChange" enum="BooleanSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Whether DnsConfigService::OnConfigChange actually corresponded to a change
     in DnsConfig.
@@ -2610,45 +2618,45 @@
 </histogram>
 
 <histogram name="AsyncDNS.ConfigNotifyInterval" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time between calls to DnsConfigService::InvalidateConfig.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.ConfigParseDuration" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>Duration of time spent parsing DnsConfig.</summary>
 </histogram>
 
 <histogram name="AsyncDNS.ConfigParsePosix" enum="AsyncDNSConfigParsePosix">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Counts of results of parsing DnsConfig in DnsConfigServicePosix.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.ConfigParseResult" enum="BooleanSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>Whether DnsConfig was parsed successfully.</summary>
 </histogram>
 
 <histogram name="AsyncDNS.ConfigParseWin" enum="AsyncDNSConfigParseWin">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Counts of results of parsing DnsConfig in DnsConfigServiceWin.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.DNSChangerDetected" enum="BooleanSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Whether the first valid DnsConfig included a rogue nameserver.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.DnsClientDisabledReason" enum="NetErrorCodes">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Counts of specific error codes returned by DnsTask if a subsequent ProcTask
     succeeded, at the end of a streak of failures after which the DnsClient was
@@ -2657,7 +2665,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.DnsClientEnabled" enum="BooleanSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     TRUE counts the events when a valid DnsConfig is received and used to enable
     DnsClient, while FALSE counts the events when DnsClient is disabled after a
@@ -2666,21 +2674,21 @@
 </histogram>
 
 <histogram name="AsyncDNS.FallbackFail" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time spent by ProcTask in failing fallback resolutions.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.FallbackSuccess" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time spent by ProcTask in successful fallback resolutions.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.HaveDnsConfig" enum="BooleanSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Whether there was a valid DNS configuration at the start of a job which
     eventually completed successfully.
@@ -2688,12 +2696,12 @@
 </histogram>
 
 <histogram name="AsyncDNS.HostParseResult" enum="BooleanSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>Whether DnsHosts were parsed successfully.</summary>
 </histogram>
 
 <histogram name="AsyncDNS.HostsChange" enum="BooleanSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Whether DnsConfigService::OnHostsChange actually corresponded to a change in
     DnsHosts.
@@ -2701,33 +2709,33 @@
 </histogram>
 
 <histogram name="AsyncDNS.HostsNotifyInterval" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time between calls to DnsConfigService::InvalidateHosts.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.HostsParseDuration" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>Duration of time spent parsing DnsHosts.</summary>
 </histogram>
 
 <histogram name="AsyncDNS.HostsParseWin" enum="AsyncDNSHostsParseWin">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Counts of results of parsing DnsHosts in DnsConfigServiceWin.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.HostsSize" units="bytes">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     The size of the HOSTS file observed before each attempt to parse it.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTime" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the time the HostResolverImpl::Job was created and the
     time the Job was started (using DnsClient).
@@ -2735,7 +2743,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTime_HIGHEST" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the time the HostResolverImpl::Job was created and the
     time the Job was started (using DnsClient). Includes only Jobs which had
@@ -2744,7 +2752,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTime_IDLE" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the time the HostResolverImpl::Job was created and the
     time the Job was started (using DnsClient). Includes only Jobs which had
@@ -2753,7 +2761,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTime_LOW" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the time the HostResolverImpl::Job was created and the
     time the Job was started (using DnsClient). Includes only Jobs which had
@@ -2762,7 +2770,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTime_LOWEST" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the time the HostResolverImpl::Job was created and the
     time the Job was started (using DnsClient). Includes only Jobs which had
@@ -2771,7 +2779,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTime_MEDIUM" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the time the HostResolverImpl::Job was created and the
     time the Job was started (using DnsClient). Includes only Jobs which had
@@ -2780,7 +2788,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTimeAfterChange" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the last time the priority of a HostResolverImpl::Job
     changed (when a Request was attached or detached) and the time the Job was
@@ -2789,7 +2797,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTimeAfterChange_HIGHEST" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the last time the priority of a HostResolverImpl::Job
     changed (when a Request was attached or detached) and the time the Job was
@@ -2799,7 +2807,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTimeAfterChange_IDLE" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the last time the priority of a HostResolverImpl::Job
     changed (when a Request was attached or detached) and the time the Job was
@@ -2809,7 +2817,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTimeAfterChange_LOW" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the last time the priority of a HostResolverImpl::Job
     changed (when a Request was attached or detached) and the time the Job was
@@ -2819,7 +2827,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTimeAfterChange_LOWEST" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the last time the priority of a HostResolverImpl::Job
     changed (when a Request was attached or detached) and the time the Job was
@@ -2829,7 +2837,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.JobQueueTimeAfterChange_MEDIUM" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Time elapsed between the last time the priority of a HostResolverImpl::Job
     changed (when a Request was attached or detached) and the time the Job was
@@ -2842,7 +2850,7 @@
   <obsolete>
     Deprecated as of 4/2016.
   </obsolete>
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Type of nameservers in the DNS config, recorded each time the config is read
     by the DNSConfigService.
@@ -2850,7 +2858,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.ParseToAddressList" enum="AsyncDNSParseResult">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Counts of results of parsing addresses out of DNS responses in successful
     DnsTransactions.
@@ -2858,7 +2866,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.PrefDefaultSource" enum="AsyncDNSPrefDefaultSource">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     The source of the async DNS preference's default. Logged at startup, when
     the IO thread is created.
@@ -2866,7 +2874,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.PrefSource" enum="AsyncDNSPrefSource">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     The source of the async DNS preference's value. Logged at startup, when the
     IO thread is created.
@@ -2874,7 +2882,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.ResolveError" enum="NetErrorCodes">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Counts of specific error codes returned by DnsTask if a subsequent ProcTask
     succeeded.
@@ -2882,7 +2890,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.ResolveFail" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken by DnsTask in resolutions that failed. Excludes time
     spent in the subsequent fallback.
@@ -2890,7 +2898,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.ResolveStatus" enum="AsyncDNSResolveStatus">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Counts of the overall results of using asynchronous DNS in HostResolverImpl.
     This only includes jobs started with valid DNS configuration and excludes
@@ -2899,35 +2907,35 @@
 </histogram>
 
 <histogram name="AsyncDNS.ResolveSuccess" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken by DnsTask in resolutions that succeeded.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.ResolveSuccess_FAMILY_IPV4" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Same as AsyncDNS.ResolveSuccess, but limited to pure IPv4 lookups.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.ResolveSuccess_FAMILY_IPV6" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Same as AsyncDNS.ResolveSuccess, but limited to pure IPv6 lookups.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.ResolveSuccess_FAMILY_UNSPEC" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Same as AsyncDNS.ResolveSuccess, but limited to IPv4/IPv6 lookups.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.ServerCount">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Count of servers in DnsConfig. Recorded on every new DnsSession, which is
     created on DNS change.
@@ -2935,14 +2943,14 @@
 </histogram>
 
 <histogram name="AsyncDNS.ServerFailureIndex">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Index in DnsConfig of the failing server, recorded at the time of failure.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.ServerFailuresAfterNetworkChange">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Count of server failures after network change before first success in the
     DnsSession. Recorded at the time of first success.
@@ -2950,7 +2958,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.ServerFailuresAfterSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Count of server failures after success until the end of the session. Server
     has reported success at some point during the session. Recorded at the end
@@ -2959,7 +2967,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.ServerFailuresBeforeSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Count of server failures before success. This is NOT the first success in
     the DnsSession. Recorded at the time of success.
@@ -2967,7 +2975,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.ServerFailuresWithoutSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Count of server failures without success until the end of the session.
     Server has never reported success during the DnsSession. Recorded at the end
@@ -2976,14 +2984,14 @@
 </histogram>
 
 <histogram name="AsyncDNS.ServerIsGood" enum="BooleanSuccess">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     The current server is &quot;good&quot; and does not have to be skipped.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.SortFailure" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken in failing calls to AddressSorter in dual-stack
     resolutions using DnsTask.
@@ -2991,7 +2999,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.SortSuccess" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken in successful calls to AddressSorter in dual-stack
     resolutions using DnsTask.
@@ -2999,7 +3007,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.SuffixSearchDone">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     The number of names from the search name list consumed during a successful
     transaction (QTYPE A only).
@@ -3007,7 +3015,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.SuffixSearchRemain">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     The number of names left on the search name list at the end of a successful
     transaction (QTYPE A only).
@@ -3015,7 +3023,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.SuffixSearchStart">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     The number of names on the search name list at the start of a transaction
     (QTYPE A only).
@@ -3023,7 +3031,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.TCPAttemptFail" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken by DnsTCPAttempt in failed attempts. Excludes
     timeouts.
@@ -3031,42 +3039,42 @@
 </histogram>
 
 <histogram name="AsyncDNS.TCPAttemptSuccess" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken by DnsTCPAttempt in successful attempts.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.TimeoutErrorHistogram" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Difference between RTT and timeout calculated using Histogram algorithm.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.TimeoutErrorHistogramUnder" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Difference between timeout calculated using Histogram algorithm and RTT.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.TimeoutErrorJacobson" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Difference between RTT and timeout calculated using Jacobson algorithm.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.TimeoutErrorJacobsonUnder" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Difference between timeout calculated using Jacobson algorithm and RTT.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.TimeoutSpentHistogram" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time that would be spent waiting for lost request using
     Histogram algorithm.
@@ -3074,7 +3082,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.TimeoutSpentJacobson" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time that would be spent waiting for lost request using Jacobson
     algorithm.
@@ -3082,7 +3090,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.TotalTime" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time since a HostResolverImpl::Resolve request to the time a
     result is posted. Excludes canceled, evicted, and aborted requests. Includes
@@ -3091,7 +3099,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.TotalTime_speculative" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time since a HostResolverImpl::Resolve request to the time a
     result is posted. Excludes canceled, evicted, and aborted requests. Includes
@@ -3100,7 +3108,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.TransactionFailure" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken in failing DnsTransactions. This includes server
     failures, timeouts and NXDOMAIN results.
@@ -3108,7 +3116,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.TransactionSuccess" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken in successful DnsTransactions. This includes all
     NOERROR answers, even if they indicate the name has no addresses or they
@@ -3117,21 +3125,21 @@
 </histogram>
 
 <histogram name="AsyncDNS.TransactionSuccess_A" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Same as AsyncDNS.TransactionSuccess but limited to A query type.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.TransactionSuccess_AAAA" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Same as AsyncDNS.TransactionSuccess but limited to AAAA query type.
   </summary>
 </histogram>
 
 <histogram name="AsyncDNS.TTL" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     TTL of the resolved addresses, as in the response received from the server.
     For results served from local cache, the TTL is from the original response.
@@ -3139,7 +3147,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.UDPAttemptFail" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken by DnsUDPAttempt in failed attempts. Excludes
     timeouts.
@@ -3147,7 +3155,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.UDPAttemptSuccess" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time taken by DnsUDPAttempt in successful attempts. Includes
     responses arriving after timeout, if multiple attempts are allowed.
@@ -3155,7 +3163,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.UnchangedConfigInterval" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time since the last empty config result to the time a non-change
     OnConfigChange is received.
@@ -3163,7 +3171,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.UnchangedHostsInterval" units="ms">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     Duration of time since the last empty config result to the time a non-change
     OnHostsChange is received.
@@ -3171,7 +3179,7 @@
 </histogram>
 
 <histogram name="AsyncDNS.WatchStatus" enum="AsyncDNSWatchStatus">
-  <owner>juliatuttle@chromium.org</owner>
+  <owner>mgersh@chromium.org</owner>
   <summary>
     The result of DnsConfigService watch. Counts STARTED on every initialization
     and FAILED_* on any failure.
@@ -16013,6 +16021,26 @@
   </summary>
 </histogram>
 
+<histogram name="Event.Latency.BlockingTime.KeyPressDefaultAllowed" units="ms">
+  <owner>tdresser@chromium.org</owner>
+  <owner>input-dev@chromium.org</owner>
+  <summary>
+    Time between the renderer main thread receiving a keyboard event and acking
+    it, for events which were not preventDefaulted. Only recorded for key
+    presses.
+  </summary>
+</histogram>
+
+<histogram name="Event.Latency.BlockingTime.KeyPressDefaultPrevented"
+    units="ms">
+  <owner>tdresser@chromium.org</owner>
+  <owner>input-dev@chromium.org</owner>
+  <summary>
+    Time between the renderer main thread receiving a keyboard event and acking
+    it, for events which were preventDefaulted. Only recorded for key presses.
+  </summary>
+</histogram>
+
 <histogram name="Event.Latency.BlockingTime.TouchEndDefaultAllowed" units="ms">
   <owner>tdresser@chromium.org</owner>
   <summary>
@@ -16439,6 +16467,24 @@
   </summary>
 </histogram>
 
+<histogram name="Event.Latency.Browser.KeyPressAcked" units="microseconds">
+  <owner>tdresser@chromium.org</owner>
+  <owner>input-dev@chromium.org</owner>
+  <summary>
+    Time between key events sent from RWH to renderer and acked by renderer.
+    Only monitors key presses.
+  </summary>
+</histogram>
+
+<histogram name="Event.Latency.Browser.KeyPressUI" units="microseconds">
+  <owner>tdresser@chromium.org</owner>
+  <owner>input-dev@chromium.org</owner>
+  <summary>
+    Time between key events received by Chrome and sent from RWH to renderer.
+    Only monitors key presses.
+  </summary>
+</histogram>
+
 <histogram name="Event.Latency.Browser.TouchAcked" units="microseconds">
   <owner>tdresser@chromium.org</owner>
   <summary>
@@ -16475,6 +16521,16 @@
   </summary>
 </histogram>
 
+<histogram name="Event.Latency.EndToEnd.Key" units="ms">
+  <owner>tdresser@chromium.org</owner>
+  <owner>input-dev@chromium.org</owner>
+  <summary>
+    Time between the OS receiving a keyboard event and the resulting GPU frame
+    swap. If no swap was induced by the event, no recording is made. Only
+    recorded for key presses.
+  </summary>
+</histogram>
+
 <histogram name="Event.Latency.HitTest" units="microseconds">
   <owner>dtapuska@chromium.org</owner>
   <summary>
@@ -16502,6 +16558,27 @@
   <summary>Time between input event received by OS and sent to Chrome.</summary>
 </histogram>
 
+<histogram name="Event.Latency.QueueingTime.KeyPressDefaultAllowed" units="ms">
+  <owner>tdresser@chromium.org</owner>
+  <owner>input-dev@chromium.org</owner>
+  <summary>
+    Time between sending a keyboard event to the renderer main thread and when
+    the renderer begins to process that event, for events which were not
+    preventDefaulted. Only recorded for key presses.
+  </summary>
+</histogram>
+
+<histogram name="Event.Latency.QueueingTime.KeyPressDefaultPrevented"
+    units="ms">
+  <owner>tdresser@chromium.org</owner>
+  <owner>input-dev@chromium.org</owner>
+  <summary>
+    Time between sending a keyboard event to the renderer main thread and when
+    the renderer begins to process that event, for events which were
+    preventDefaulted. Only recorded for key presses.
+  </summary>
+</histogram>
+
 <histogram name="Event.Latency.QueueingTime.TouchEndDefaultAllowed" units="ms">
   <owner>tdresser@chromium.org</owner>
   <summary>
@@ -47426,7 +47503,8 @@
     units="Ad frames">
   <owner>jkarlin@chromium.org</owner>
   <summary>
-    The number of frames on the page identified as Google Ad Frames.
+    The number of frames on the page identified as Google Ad Frames that have
+    loaded more than 0 bytes of content.
 
     For pages with zero ad frames, the other PageLoad.Clients.Ads metrics are
     not recorded unless otherwise specified.
@@ -47438,6 +47516,9 @@
 <histogram
     name="PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.AdFrames"
     units="Frames">
+  <obsolete>
+    Deprecated In May 2017.
+  </obsolete>
   <owner>jkarlin@chromium.org</owner>
   <summary>
     The number of frames (with parent frame of main frame) that are on the page
@@ -47451,6 +47532,9 @@
 <histogram
     name="PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.PercentAdFrames"
     units="%">
+  <obsolete>
+    Deprecated In May 2017.
+  </obsolete>
   <owner>jkarlin@chromium.org</owner>
   <summary>
     The percentage of frames (with parent frame of main frame) on the page that
@@ -47465,6 +47549,9 @@
 <histogram
     name="PageLoad.Clients.Ads.Google.FrameCounts.MainFrameParent.TotalFrames"
     units="Frames">
+  <obsolete>
+    Deprecated In May 2017.
+  </obsolete>
   <owner>jkarlin@chromium.org</owner>
   <summary>
     The number of frames (with parent frame of main frame) on the page.
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index b040b25f..3408224 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -129,7 +129,6 @@
 startup.warm.chrome_signin,,
 storage.indexeddb_endure,cmumford@chromium.org,
 storage.indexeddb_endure_tracing,cmumford@chromium.org,
-sunspider,"bmeurer@chromium.org, mvstanton@chromium.org",
 system_health.common_desktop,"charliea@chromium.org, nednguyen@chromium.org",
 system_health.common_mobile,"charliea@chromium.org, nednguyen@chromium.org",
 system_health.memory_desktop,perezju@chromium.org,
diff --git a/tools/perf/benchmarks/sunspider.py b/tools/perf/benchmarks/sunspider.py
deleted file mode 100644
index 31916b2..0000000
--- a/tools/perf/benchmarks/sunspider.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# Copyright 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-import collections
-import json
-import os
-
-from core import perf_benchmark
-
-from telemetry import benchmark
-from telemetry import page as page_module
-from telemetry.page import legacy_page_test
-from telemetry import story
-from telemetry.value import list_of_scalar_values
-
-from metrics import power
-
-
-_URL = 'http://www.webkit.org/perf/sunspider-1.0.2/sunspider-1.0.2/driver.html'
-
-DESCRIPTIONS = {
-    '3d-cube':
-        'Pure JavaScript computations of the kind you might use to do 3d '
-        'rendering, but without the rendering. This ends up mostly hitting '
-        'floating point math and array access.',
-    '3d-morph':
-        'Pure JavaScript computations of the kind you might use to do 3d '
-        'rendering, but without the rendering. This ends up mostly hitting '
-        'floating point math and array access.',
-    '3d-raytrace':
-        'Pure JavaScript computations of the kind you might use to do 3d '
-        'rendering, but without the rendering. This ends up mostly hitting '
-        'floating point math and array access.',
-    'access-binary-trees': 'Array, object property and variable access.',
-    'access-fannkuch': 'Array, object property and variable access.',
-    'access-nbody': 'Array, object property and variable access.',
-    'access-nsieve': 'Array, object property and variable access.',
-    'bitops-3bit-bits-in-byte':
-        'Bitwise operations, these can be useful for various things '
-        'including games, mathematical computations, and various kinds of '
-        'encoding/decoding. It\'s also the only kind of math in JavaScript '
-        'that is done as integer, not floating point.',
-    'bitops-bits-in-byte':
-        'Bitwise operations, these can be useful for various things '
-        'including games, mathematical computations, and various kinds of '
-        'encoding/decoding. It\'s also the only kind of math in JavaScript '
-        'that is done as integer, not floating point.',
-    'bitops-bitwise-and':
-        'Bitwise operations, these can be useful for various things '
-        'including games, mathematical computations, and various kinds of '
-        'encoding/decoding. It\'s also the only kind of math in JavaScript '
-        'that is done as integer, not floating point.',
-    'bitops-nsieve-bits':
-        'Bitwise operations, these can be useful for various things '
-        'including games, mathematical computations, and various kinds of '
-        'encoding/decoding. It\'s also the only kind of math in JavaScript '
-        'that is done as integer, not floating point.',
-    'controlflow-recursive':
-        'Control flow constructs (looping, recursion, conditionals). Right '
-        'now it mostly covers recursion, as the others are pretty well covered '
-        'by other tests.',
-    'crypto-aes': 'Real cryptography code related to AES.',
-    'crypto-md5': 'Real cryptography code related to MD5.',
-    'crypto-sha1': 'Real cryptography code related to SHA1.',
-    'date-format-tofte': 'Performance of JavaScript\'s "date" objects.',
-    'date-format-xparb': 'Performance of JavaScript\'s "date" objects.',
-    'math-cordic': 'Various mathematical type computations.',
-    'math-partial-sums': 'Various mathematical type computations.',
-    'math-spectral-norm': 'Various mathematical type computations.',
-    'regexp-dna': 'Regular expressions performance.',
-    'string-base64': 'String processing.',
-    'string-fasta': 'String processing',
-    'string-tagcloud': 'String processing code to generate a giant "tagcloud".',
-    'string-unpack-code': 'String processing code to extracting compressed JS.',
-    'string-validate-input': 'String processing.',
-}
-
-
-class _SunspiderMeasurement(legacy_page_test.LegacyPageTest):
-
-  def __init__(self):
-    super(_SunspiderMeasurement, self).__init__()
-    self._power_metric = None
-
-  def CustomizeBrowserOptions(self, options):
-    power.PowerMetric.CustomizeBrowserOptions(options)
-
-  def WillStartBrowser(self, platform):
-    self._power_metric = power.PowerMetric(platform)
-
-  def DidNavigateToPage(self, page, tab):
-    self._power_metric.Start(page, tab)
-
-  def ValidateAndMeasurePage(self, page, tab, results):
-    tab.WaitForJavaScriptCondition(
-        'window.location.pathname.indexOf("results.html") >= 0'
-        '&& typeof(output) != "undefined"', timeout=300)
-
-    self._power_metric.Stop(page, tab)
-    self._power_metric.AddResults(tab, results)
-
-    js_results = json.loads(tab.EvaluateJavaScript('JSON.stringify(output);'))
-
-    # Below, r is a map of benchmark names to lists of result numbers,
-    # and totals is a list of totals of result numbers.
-    # js_results is: formatted like this:
-    # [
-    #   {'3d-cube': v1, '3d-morph': v2, ...},
-    #   {'3d-cube': v3, '3d-morph': v4, ...},
-    #   ...
-    # ]
-    r = collections.defaultdict(list)
-    totals = []
-    for result in js_results:
-      total = 0
-      for key, value in result.iteritems():
-        r[key].append(value)
-        total += value
-      totals.append(total)
-    for key, values in r.iteritems():
-      results.AddValue(list_of_scalar_values.ListOfScalarValues(
-          results.current_page, key, 'ms', values, important=False,
-          description=DESCRIPTIONS.get(key)))
-    results.AddValue(list_of_scalar_values.ListOfScalarValues(
-        results.current_page, 'Total', 'ms', totals,
-        description='Totals of run time for each different type of benchmark '
-                    'in sunspider'))
-
-
-@benchmark.Disabled('all')  # crbug.com/712208
-@benchmark.Owner(emails=['bmeurer@chromium.org', 'mvstanton@chromium.org'])
-class Sunspider(perf_benchmark.PerfBenchmark):
-  """Apple's SunSpider JavaScript benchmark.
-
-  http://www.webkit.org/perf/sunspider/sunspider.html
-  """
-  test = _SunspiderMeasurement
-
-  @classmethod
-  def Name(cls):
-    return 'sunspider'
-
-  def CreateStorySet(self, options):
-    ps = story.StorySet(
-        archive_data_file='../page_sets/data/sunspider.json',
-        base_dir=os.path.dirname(os.path.abspath(__file__)),
-        cloud_storage_bucket=story.PARTNER_BUCKET)
-    ps.AddStory(page_module.Page(
-        _URL, ps, ps.base_dir, make_javascript_deterministic=False))
-    return ps
diff --git a/tools/perf/page_sets/system_health/browsing_stories.py b/tools/perf/page_sets/system_health/browsing_stories.py
index e949881..75ba50a2 100644
--- a/tools/perf/page_sets/system_health/browsing_stories.py
+++ b/tools/perf/page_sets/system_health/browsing_stories.py
@@ -199,7 +199,6 @@
   SUPPORTED_PLATFORMS = platforms.MOBILE_ONLY
 
 
-@decorators.Disabled('mac')  # crbug.com/722094
 class TwitterMobileStory(_ArticleBrowsingStory):
   NAME = 'browse:social:twitter'
   URL = 'https://www.twitter.com/nasa'
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index f0d256a..1fa2766 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -62,6 +62,8 @@
     "env_input_state_controller.cc",
     "env_input_state_controller.h",
     "env_observer.h",
+    "event_injector.cc",
+    "event_injector.h",
     "input_state_lookup.cc",
     "input_state_lookup.h",
     "input_state_lookup_win.cc",
diff --git a/ui/aura/env.h b/ui/aura/env.h
index 31f7366..303e7b6 100644
--- a/ui/aura/env.h
+++ b/ui/aura/env.h
@@ -114,6 +114,7 @@
 
  private:
   friend class test::EnvTestHelper;
+  friend class EventInjector;
   friend class MusMouseLocationUpdater;
   friend class Window;
   friend class WindowTreeHost;
diff --git a/ui/aura/event_injector.cc b/ui/aura/event_injector.cc
new file mode 100644
index 0000000..09019d0d
--- /dev/null
+++ b/ui/aura/event_injector.cc
@@ -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.
+
+#include "ui/aura/event_injector.h"
+
+#include "services/service_manager/public/cpp/connector.h"
+#include "services/ui/public/interfaces/constants.mojom.h"
+#include "ui/aura/env.h"
+#include "ui/aura/mus/window_tree_client.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
+#include "ui/events/event.h"
+#include "ui/events/event_sink.h"
+
+namespace {
+std::unique_ptr<ui::Event> MapEvent(const ui::Event& event) {
+  if (event.IsScrollEvent()) {
+    return base::MakeUnique<ui::PointerEvent>(
+        ui::MouseWheelEvent(*event.AsScrollEvent()));
+  }
+
+  if (event.IsMouseEvent())
+    return base::MakeUnique<ui::PointerEvent>(*event.AsMouseEvent());
+
+  if (event.IsTouchEvent())
+    return base::MakeUnique<ui::PointerEvent>(*event.AsTouchEvent());
+
+  return ui::Event::Clone(event);
+}
+
+}  // namespace
+
+namespace aura {
+
+EventInjector::EventInjector() {}
+
+EventInjector::~EventInjector() {}
+
+ui::EventDispatchDetails EventInjector::Inject(WindowTreeHost* host,
+                                               ui::Event* event) {
+  Env* env = Env::GetInstance();
+  DCHECK(env);
+  DCHECK(host);
+  DCHECK(event);
+
+  if (env->mode() == Env::Mode::LOCAL)
+    return host->event_sink()->OnEventFromSource(event);
+  if (!window_server_ptr_) {
+    env->window_tree_client_->connector()->BindInterface(
+        ui::mojom::kServiceName, &window_server_ptr_);
+  }
+  display::Screen* screen = display::Screen::GetScreen();
+  window_server_ptr_->DispatchEvent(
+      screen->GetDisplayNearestWindow(host->window()).id(), MapEvent(*event),
+      base::Bind([](bool result) { DCHECK(result); }));
+  return ui::EventDispatchDetails();
+}
+
+}  // namespace aura
diff --git a/ui/aura/event_injector.h b/ui/aura/event_injector.h
new file mode 100644
index 0000000..a1da299
--- /dev/null
+++ b/ui/aura/event_injector.h
@@ -0,0 +1,38 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_AURA_EVENT_INJECTOR_H_
+#define UI_AURA_EVENT_INJECTOR_H_
+
+#include "services/ui/public/interfaces/window_server_test.mojom.h"
+#include "ui/aura/aura_export.h"
+
+namespace ui {
+class Event;
+struct EventDispatchDetails;
+}
+
+namespace aura {
+
+class WindowTreeHost;
+
+// Used to inject events into the system. In LOCAL mode, it directly injects
+// events into the WindowTreeHost, but in MUS mode, it injects events into the
+// window-server (over the mojom API).
+class AURA_EXPORT EventInjector {
+ public:
+  EventInjector();
+  ~EventInjector();
+
+  ui::EventDispatchDetails Inject(WindowTreeHost* host, ui::Event* event);
+
+ private:
+  ui::mojom::WindowServerTestPtr window_server_ptr_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventInjector);
+};
+
+}  // namespace aura
+
+#endif  // UI_AURA_EVENT_INJECTOR_H_
diff --git a/ui/events/event.cc b/ui/events/event.cc
index ce99ff82..249781f 100644
--- a/ui/events/event.cc
+++ b/ui/events/event.cc
@@ -149,13 +149,18 @@
     case ET_SCROLL_FLING_CANCEL:
       return SourceEventType::UNKNOWN;
 
+    case ui::ET_KEY_PRESSED:
+      return ui::SourceEventType::KEY_PRESS;
+
     case ET_MOUSE_PRESSED:
     case ET_MOUSE_DRAGGED:
     case ET_MOUSE_RELEASED:
     case ET_MOUSE_MOVED:
     case ET_MOUSE_ENTERED:
     case ET_MOUSE_EXITED:
-    case ET_KEY_PRESSED:
+    // We measure latency for key presses, not key releases. Most behavior is
+    // keyed off of presses, and release latency is higher than press latency as
+    // it's impacted by event handling of the press event.
     case ET_KEY_RELEASED:
     case ET_MOUSE_CAPTURE_CHANGED:
     case ET_DROP_TARGET_EVENT:
@@ -1141,6 +1146,11 @@
       key_code_(KeyboardCodeFromNative(native_event)),
       code_(CodeFromNative(native_event)),
       is_char_(IsCharFromNative(native_event)) {
+  latency()->AddLatencyNumberWithTimestamp(
+      INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, 0,
+      base::TimeTicks::FromInternalValue(time_stamp().ToInternalValue()), 1);
+  latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+
   if (IsRepeated(*this))
     set_flags(flags() | ui::EF_IS_REPEAT);
 
diff --git a/ui/events/ozone/gamepad/gamepad_event.h b/ui/events/ozone/gamepad/gamepad_event.h
index c0c5349..61e32bd 100644
--- a/ui/events/ozone/gamepad/gamepad_event.h
+++ b/ui/events/ozone/gamepad/gamepad_event.h
@@ -18,15 +18,15 @@
                double value,
                base::TimeTicks timestamp);
 
-  int device_id() { return device_id_; }
+  int device_id() const { return device_id_; }
 
-  GamepadEventType type() { return type_; }
+  GamepadEventType type() const { return type_; }
 
-  uint16_t code() { return code_; }
+  uint16_t code() const { return code_; }
 
-  double value() { return value_; }
+  double value() const { return value_; }
 
-  base::TimeTicks timestamp() { return timestamp_; }
+  base::TimeTicks timestamp() const { return timestamp_; }
 
  private:
   int device_id_;
diff --git a/ui/latency/latency_info.h b/ui/latency/latency_info.h
index 84c5434..0f8b6be 100644
--- a/ui/latency/latency_info.h
+++ b/ui/latency/latency_info.h
@@ -109,6 +109,7 @@
   UNKNOWN,
   WHEEL,
   TOUCH,
+  KEY_PRESS,
   OTHER,
   SOURCE_EVENT_TYPE_LAST = OTHER,
 };
diff --git a/ui/latency/latency_tracker.cc b/ui/latency/latency_tracker.cc
index 20a30f1..d402506d 100644
--- a/ui/latency/latency_tracker.cc
+++ b/ui/latency/latency_tracker.cc
@@ -18,6 +18,8 @@
       return "Wheel";
     case ui::SourceEventType::TOUCH:
       return "Touch";
+    case ui::SourceEventType::KEY_PRESS:
+      return "KeyPress";
     default:
       return "";
   }
@@ -92,9 +94,10 @@
 
   ui::SourceEventType source_event_type = latency.source_event_type();
   if (source_event_type == ui::SourceEventType::WHEEL ||
-      source_event_type == ui::SourceEventType::TOUCH) {
-    ComputeTouchAndWheelScrollLatencyHistograms(
-        gpu_swap_begin_component, gpu_swap_end_component, latency);
+      source_event_type == ui::SourceEventType::TOUCH ||
+      source_event_type == ui::SourceEventType::KEY_PRESS) {
+    ComputeEndToEndLatencyHistograms(gpu_swap_begin_component,
+                                     gpu_swap_end_component, latency);
   }
 
   // Compute the old scroll update latency metrics. They are exclusively
@@ -117,7 +120,7 @@
   // Mus.
 }
 
-void LatencyTracker::ComputeTouchAndWheelScrollLatencyHistograms(
+void LatencyTracker::ComputeEndToEndLatencyHistograms(
     const ui::LatencyInfo::LatencyComponent& gpu_swap_begin_component,
     const ui::LatencyInfo::LatencyComponent& gpu_swap_end_component,
     const ui::LatencyInfo& latency) {
@@ -168,6 +171,13 @@
           "Event.Latency.ScrollUpdate.Touch.TimeToScrollUpdateSwapBegin2",
           original_component, gpu_swap_begin_component);
     }
+  } else if (latency.FindLatency(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0,
+                                 &original_component)) {
+    if (input_modality == "KeyPress") {
+      UMA_HISTOGRAM_INPUT_LATENCY_HIGH_RESOLUTION_MICROSECONDS(
+          "Event.Latency.EndToEnd.KeyPress", original_component,
+          gpu_swap_begin_component);
+    }
   } else {
     // No original component found.
     return;
diff --git a/ui/latency/latency_tracker.h b/ui/latency/latency_tracker.h
index 0fae6d3..4d0a432 100644
--- a/ui/latency/latency_tracker.h
+++ b/ui/latency/latency_tracker.h
@@ -29,7 +29,7 @@
       const LatencyInfo::LatencyComponent& end_component);
 
  private:
-  void ComputeTouchAndWheelScrollLatencyHistograms(
+  void ComputeEndToEndLatencyHistograms(
       const LatencyInfo::LatencyComponent& gpu_swap_begin_component,
       const LatencyInfo::LatencyComponent& gpu_swap_end_component,
       const LatencyInfo& latency);
diff --git a/ui/latency/mojo/latency_info.mojom b/ui/latency/mojo/latency_info.mojom
index 0b985e2..44be9a7 100644
--- a/ui/latency/mojo/latency_info.mojom
+++ b/ui/latency/mojo/latency_info.mojom
@@ -79,6 +79,7 @@
   UNKNOWN,
   WHEEL,
   TOUCH,
+  KEY_PRESS,
   OTHER,
   SOURCE_EVENT_TYPE_LAST = OTHER,
 };
diff --git a/ui/latency/mojo/latency_info_struct_traits.cc b/ui/latency/mojo/latency_info_struct_traits.cc
index e07ecc51..12daf334 100644
--- a/ui/latency/mojo/latency_info_struct_traits.cc
+++ b/ui/latency/mojo/latency_info_struct_traits.cc
@@ -160,6 +160,8 @@
       return ui::mojom::SourceEventType::WHEEL;
     case ui::TOUCH:
       return ui::mojom::SourceEventType::TOUCH;
+    case ui::KEY_PRESS:
+      return ui::mojom::SourceEventType::KEY_PRESS;
     case ui::OTHER:
       return ui::mojom::SourceEventType::OTHER;
   }
@@ -175,6 +177,8 @@
       return ui::WHEEL;
     case ui::mojom::SourceEventType::TOUCH:
       return ui::TOUCH;
+    case ui::mojom::SourceEventType::KEY_PRESS:
+      return ui::KEY_PRESS;
     case ui::mojom::SourceEventType::OTHER:
       return ui::OTHER;
   }