diff --git a/BUILD.gn b/BUILD.gn
index 0208c10..cb75614 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -403,7 +403,6 @@
       "//base:base_junit_tests",
       "//base/android/linker:chromium_android_linker",
       "//build/android/gyp/test:hello_world",
-      "//build/android/rezip",
       "//chrome/android/webapk/shell_apk:webapk",
       "//components/invalidation/impl:components_invalidation_impl_junit_tests",
       "//components/policy/android:components_policy_junit_tests",
diff --git a/DEPS b/DEPS
index fed9fc00..5bf0552 100644
--- a/DEPS
+++ b/DEPS
@@ -40,7 +40,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'fa9193dcbe813e32376acb2411bfbb2420d03c28',
+  'skia_revision': '09d994ecb30de2e62a31af2c16307af31fe0e0b3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # 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': 'f3dc14e52faef3d279d6bdab4bff8ec4afd5eb1a',
+  'catapult_revision': '96a0b334dcf12efc0b723a4640afb662e1274557',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index 7acc3cea..2d35d157 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -610,10 +610,12 @@
     "java/src/org/chromium/android_webview/policy/AwPolicyProvider.java",
   ]
   deps = [
+    ":android_webview_crash_services_java",
     ":resources",
     ":strings_grd",
     "//base:base_java",
     "//components/autofill/android:autofill_java",
+    "//components/minidump_uploader:minidump_uploader_java",
     "//components/navigation_interception/android:navigation_interception_java",
     "//components/policy/android:policy_java",
     "//components/web_contents_delegate_android:web_contents_delegate_android_java",
@@ -695,6 +697,31 @@
   ]
 }
 
+# Keep crash services separate from other WebView code to keep their deps clean
+# (and make them easy to move).
+android_library("android_webview_crash_services_java") {
+  java_files = [
+    "java/src/org/chromium/android_webview/crash/CrashReceiverService.java",
+    "java/src/org/chromium/android_webview/crash/MinidumpUploaderImpl.java",
+    "java/src/org/chromium/android_webview/crash/MinidumpUploader.java",
+    "java/src/org/chromium/android_webview/crash/MinidumpUploadJobService.java",
+    "java/src/org/chromium/android_webview/crash/SynchronizedWebViewCommandLine.java",
+  ]
+  deps = [
+    "//base:base_java",
+    "//components/minidump_uploader:minidump_uploader_java",
+  ]
+
+  srcjar_deps = [ ":crash_receiver_aidl" ]
+}
+
+android_aidl("crash_receiver_aidl") {
+  import_include = "java/src"
+  sources = [
+    "java/src/org/chromium/android_webview/crash/ICrashReceiverService.aidl",
+  ]
+}
+
 if (!use_webview_internal_framework) {
   system_webview_apk_tmpl("system_webview_apk") {
     android_manifest = system_webview_android_manifest
diff --git a/android_webview/apk/java/AndroidManifest.xml b/android_webview/apk/java/AndroidManifest.xml
index fc60d266..0e0ac96 100644
--- a/android_webview/apk/java/AndroidManifest.xml
+++ b/android_webview/apk/java/AndroidManifest.xml
@@ -37,6 +37,13 @@
                     android:authorities="{{ manifest_package }}.LicenseContentProvider" />
             <meta-data android:name="com.android.webview.WebViewLibrary"
                     android:value="{{ webview_lib }}" />
+            <service android:name="org.chromium.android_webview.crash.CrashReceiverService"
+                     android:exported="true"
+                     android:process=":crash_receiver_service"/>
+            <service android:name="org.chromium.android_webview.crash.MinidumpUploadJobService"
+                     android:permission="android.permission.BIND_JOB_SERVICE"
+                     android:exported="true"
+                     android:process=":crash_receiver_service"/>
         {% endmacro %}
         {{ common(package|default('com.android.webview'), 'libwebviewchromium.so') }}
         <meta-data android:name="org.chromium.content.browser.NUM_SANDBOXED_SERVICES"
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
index 1b51853..867442d88 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
@@ -403,6 +403,7 @@
         final boolean isExternalService = true;
         AwBrowserProcess.configureChildProcessLauncher(webViewPackageName, isExternalService);
         AwBrowserProcess.start();
+        AwBrowserProcess.handleMinidumps(webViewPackageName);
 
         if (isBuildDebuggable()) {
             setWebContentsDebuggingEnabled(true);
diff --git a/android_webview/java/DEPS b/android_webview/java/DEPS
index 1db2afc..b1a7369a 100644
--- a/android_webview/java/DEPS
+++ b/android_webview/java/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "+content/public/android/java",
   "+components/autofill/android/java",
+  "+components/minidump_uploader/android/java",
   "+components/navigation_interception/android/java",
   "+components/policy/android/java",
   "+components/web_contents_delegate_android/android/java",
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
index 797f843..656ad9f 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
@@ -4,10 +4,18 @@
 
 package org.chromium.android_webview;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.os.AsyncTask;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.os.StrictMode;
 
+import org.chromium.android_webview.crash.CrashReceiverService;
+import org.chromium.android_webview.crash.ICrashReceiverService;
 import org.chromium.android_webview.policy.AwPolicyProvider;
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
@@ -18,12 +26,14 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.components.minidump_uploader.CrashFileManager;
 import org.chromium.content.browser.BrowserStartupController;
 import org.chromium.content.browser.ChildProcessCreationParams;
 import org.chromium.content.browser.ChildProcessLauncher;
 import org.chromium.policy.CombinedPolicyProvider;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.channels.FileLock;
@@ -39,6 +49,8 @@
     private static RandomAccessFile sLockFile;
     private static FileLock sExclusiveFileLock;
 
+    private static final int MAX_MINIDUMP_UPLOAD_TRIES = 3;
+
     /**
      * Loads the native library, and performs basic static construction of objects needed
      * to run webview in this process. Does not create threads; safe to call from zygote.
@@ -161,4 +173,78 @@
             StrictMode.setThreadPolicy(oldPolicy);
         }
     }
+
+    /**
+     * Pass Minidumps to a separate Service declared in the WebView provider package.
+     * That Service will copy the Minidumps to its own data directory - at which point we can delete
+     * our copies in the app directory.
+     */
+    public static void handleMinidumps(final String webViewPackageName) {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                final Context appContext = ContextUtils.getApplicationContext();
+                final CrashFileManager crashFileManager =
+                        new CrashFileManager(appContext.getCacheDir());
+                final File[] minidumpFiles =
+                        crashFileManager.getAllMinidumpFiles(MAX_MINIDUMP_UPLOAD_TRIES);
+                if (minidumpFiles.length == 0) return null;
+
+                final Intent intent = new Intent();
+                intent.setClassName(webViewPackageName, CrashReceiverService.class.getName());
+
+                ServiceConnection connection = new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName className, IBinder service) {
+                        // Pass file descriptors, pointing to our minidumps, to the minidump-copying
+                        // service so that the contents of the minidumps will be copied to WebView's
+                        // data directory. Delete our direct File-references to the minidumps after
+                        // creating the file-descriptors to resign from retrying to copy the
+                        // minidumps if anything goes wrong - this makes sense given that a failure
+                        // to copy a file usually means that retrying won't succeed either, e.g. the
+                        // disk being full, or the file system being corrupted.
+                        final ParcelFileDescriptor[] minidumpFds =
+                                new ParcelFileDescriptor[minidumpFiles.length];
+                        try {
+                            for (int i = 0; i < minidumpFiles.length; ++i) {
+                                try {
+                                    minidumpFds[i] = ParcelFileDescriptor.open(
+                                            minidumpFiles[i], ParcelFileDescriptor.MODE_READ_ONLY);
+                                } catch (FileNotFoundException e) {
+                                    minidumpFds[i] = null; // This is slightly ugly :)
+                                }
+                                if (!minidumpFiles[i].delete()) {
+                                    Log.w(TAG, "Couldn't delete file "
+                                            + minidumpFiles[i].getAbsolutePath());
+                                }
+                            }
+                            try {
+                                ICrashReceiverService.Stub.asInterface(service).transmitCrashes(
+                                        minidumpFds);
+                            } catch (RemoteException e) {
+                                // TODO(gsennton): add a UMA metric here to ensure we aren't losing
+                                // too many minidumps because of this.
+                            }
+                        } finally {
+                            // Close FDs
+                            for (int i = 0; i < minidumpFds.length; ++i) {
+                                try {
+                                    if (minidumpFds[i] != null) minidumpFds[i].close();
+                                } catch (IOException e) {
+                                }
+                            }
+                            appContext.unbindService(this);
+                        }
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName className) {}
+                };
+                if (!appContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
+                    Log.w(TAG, "Could not bind to Minidump-copying Service " + intent);
+                }
+                return null;
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+    }
 }
diff --git a/android_webview/java/src/org/chromium/android_webview/crash/CrashReceiverService.java b/android_webview/java/src/org/chromium/android_webview/crash/CrashReceiverService.java
new file mode 100644
index 0000000..f3b994e
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/crash/CrashReceiverService.java
@@ -0,0 +1,247 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview.crash;
+
+import android.annotation.TargetApi;
+import android.app.Service;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+
+import org.chromium.base.Log;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.components.minidump_uploader.CrashFileManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Service that is responsible for receiving crash dumps from an application, for upload.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class CrashReceiverService extends Service {
+    private static final String TAG = "CrashReceiverService";
+
+    private static final String WEBVIEW_CRASH_DIR = "WebView_Crashes";
+    private static final String WEBVIEW_TMP_CRASH_DIR = "WebView_Crashes_Tmp";
+
+    private static final int MINIDUMP_UPLOADING_JOB_ID = 42;
+    // Initial back-off time for upload-job, this is set to a fairly high number (30 minutes) to
+    // increase the chance of performing uploads in batches if the initial upload fails.
+    private static final int JOB_BACKOFF_TIME_IN_MS = 1000 * 60 * 30;
+    // Back-off policy for upload-job.
+    private static final int JOB_BACKOFF_POLICY = JobInfo.BACKOFF_POLICY_EXPONENTIAL;
+
+    private Object mCopyingLock = new Object();
+    private boolean mIsCopying = false;
+
+    // same switch as kEnableCrashReporterForTesting in //base/base_switches.h
+    static final String CRASH_UPLOADS_ENABLED_FOR_TESTING_SWITCH =
+            "enable-crash-reporter-for-testing";
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        SynchronizedWebViewCommandLine.initOnSeparateThread();
+    }
+
+    private final ICrashReceiverService.Stub mBinder = new ICrashReceiverService.Stub() {
+        @Override
+        public void transmitCrashes(ParcelFileDescriptor[] fileDescriptors) {
+            // TODO(gsennton): replace this check with a check for Android Checkbox when we have
+            // access to that value through GmsCore.
+            if (!SynchronizedWebViewCommandLine.hasSwitch(
+                        CRASH_UPLOADS_ENABLED_FOR_TESTING_SWITCH)) {
+                Log.i(TAG, "Crash reporting is not enabled, bailing!");
+                return;
+            }
+            int uid = Binder.getCallingUid();
+            performMinidumpCopyingSerially(
+                    CrashReceiverService.this, uid, fileDescriptors, true /* scheduleUploads */);
+        }
+    };
+
+    /**
+     * Copies minidumps in a synchronized way, waiting for any already started copying operations to
+     * finish before copying the current dumps.
+     * @param scheduleUploads whether to ask JobScheduler to schedule an upload-job (avoid this
+     * during testing).
+     */
+    @VisibleForTesting
+    public void performMinidumpCopyingSerially(Context context, int uid,
+            ParcelFileDescriptor[] fileDescriptors, boolean scheduleUploads) {
+        if (!waitUntilWeCanCopy()) {
+            Log.e(TAG, "something went wrong when waiting to copy minidumps, bailing!");
+            return;
+        }
+
+        try {
+            boolean copySucceeded = copyMinidumps(context, uid, fileDescriptors);
+            if (copySucceeded && scheduleUploads) {
+                // Only schedule a new job if there actually are any files to upload.
+                scheduleNewJobIfNoJobsActive();
+            }
+        } finally {
+            synchronized (mCopyingLock) {
+                mIsCopying = false;
+                mCopyingLock.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Wait until we are allowed to copy minidumps.
+     * @return whether we are actually allowed to copy the files - if false we should just bail.
+     */
+    private boolean waitUntilWeCanCopy() {
+        synchronized (mCopyingLock) {
+            while (mIsCopying) {
+                try {
+                    mCopyingLock.wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Was interrupted when waiting to copy minidumps", e);
+                    return false;
+                }
+            }
+            mIsCopying = true;
+            return true;
+        }
+    }
+
+    /**
+     * @return the currently pending job with ID MINIDUMP_UPLOADING_JOB_ID, or null if no such job
+     * exists.
+     */
+    private static JobInfo getCurrentPendingJob(JobScheduler jobScheduler) {
+        List<JobInfo> pendingJobs = jobScheduler.getAllPendingJobs();
+        for (JobInfo job : pendingJobs) {
+            if (job.getId() == MINIDUMP_UPLOADING_JOB_ID) return job;
+        }
+        return null;
+    }
+
+    private void scheduleNewJobIfNoJobsActive() {
+        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        if (getCurrentPendingJob(jobScheduler) != null) {
+            return;
+        }
+        JobInfo newJob = new JobInfo
+                .Builder(MINIDUMP_UPLOADING_JOB_ID /* jobId */,
+                    new ComponentName(this, MinidumpUploadJobService.class))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                // Minimum delay when a job is retried (a retry will happen when there are minidumps
+                // left after trying to upload all minidumps - this could e.g. happen if we add more
+                // minidumps at the same time as uploading old ones).
+                .setBackoffCriteria(JOB_BACKOFF_TIME_IN_MS, JOB_BACKOFF_POLICY)
+                .build();
+        if (jobScheduler.schedule(newJob) == JobScheduler.RESULT_FAILURE) {
+            throw new RuntimeException("couldn't schedule " + newJob);
+        }
+    }
+
+    /**
+     * Copy minidumps from the {@param fileDescriptors} to the directory where WebView stores its
+     * minidump files. {@param context} is used to look up the directory in which the files will be
+     * saved.
+     * @return whether any minidump was copied.
+     */
+    @VisibleForTesting
+    public static boolean copyMinidumps(
+            Context context, int uid, ParcelFileDescriptor[] fileDescriptors) {
+        CrashFileManager crashFileManager = new CrashFileManager(createWebViewCrashDir(context));
+        boolean copiedAnything = false;
+        if (fileDescriptors != null) {
+            for (ParcelFileDescriptor fd : fileDescriptors) {
+                if (fd == null) continue;
+                try {
+                    File copiedFile = crashFileManager.copyMinidumpFromFD(fd.getFileDescriptor(),
+                            getWebViewTmpCrashDir(context), uid);
+                    if (copiedFile == null) {
+                        Log.w(TAG, "failed to copy minidump from " + fd.toString());
+                        // TODO(gsennton): add UMA metric to ensure we aren't losing too many
+                        // minidumps here.
+                    } else {
+                        copiedAnything = true;
+                    }
+                } catch (IOException e) {
+                    Log.w(TAG, "failed to copy minidump from " + fd.toString() + ": "
+                            + e.getMessage());
+                } finally {
+                    deleteFilesInWebViewTmpDirIfExists(context);
+                }
+            }
+        }
+        return copiedAnything;
+    }
+
+    /**
+     * Delete all files in the directory where temporary files from this Service are stored.
+     */
+    @VisibleForTesting
+    public static void deleteFilesInWebViewTmpDirIfExists(Context context) {
+        deleteFilesInDirIfExists(getWebViewTmpCrashDir(context));
+    }
+
+    private static void deleteFilesInDirIfExists(File directory) {
+        if (directory.isDirectory()) {
+            File[] files = directory.listFiles();
+            if (files != null) {
+                for (File file : files) {
+                    if (!file.delete()) {
+                        Log.w(TAG, "Couldn't delete file " + file.getAbsolutePath());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Create the directory in which WebView wlll store its minidumps.
+     * WebView needs a crash directory different from Chrome's to ensure Chrome's and WebView's
+     * minidump handling won't clash in cases where both Chrome and WebView are provided by the
+     * same app (Monochrome).
+     * @param context Android Context used to find a cache-directory where minidumps can be stored.
+     * @return a reference to the created directory, or null if the creation failed.
+     */
+    @VisibleForTesting
+    public static File createWebViewCrashDir(Context context) {
+        File dir = getWebViewCrashDir(context);
+        if (dir.isDirectory() || dir.mkdirs()) {
+            return dir;
+        }
+        return null;
+    }
+
+    /**
+     * Fetch the crash directory where WebView stores its minidumps.
+     * @param context Android Context used to find a cache-directory where minidumps can be stored.
+     * @return a File pointing to the crash directory.
+     */
+    @VisibleForTesting
+    public static File getWebViewCrashDir(Context context) {
+        return new File(context.getCacheDir(), WEBVIEW_CRASH_DIR);
+    }
+
+    /**
+     * Directory where we store files temporarily when copying from an app process.
+     * @param context Android Context used to find a cache-directory where minidumps can be stored.
+     */
+    @VisibleForTesting
+    public static File getWebViewTmpCrashDir(Context context) {
+        return new File(context.getCacheDir(), WEBVIEW_TMP_CRASH_DIR);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/android_webview/java/src/org/chromium/android_webview/crash/ICrashReceiverService.aidl b/android_webview/java/src/org/chromium/android_webview/crash/ICrashReceiverService.aidl
new file mode 100644
index 0000000..79d8404
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/crash/ICrashReceiverService.aidl
@@ -0,0 +1,9 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview.crash;
+
+interface ICrashReceiverService {
+    void transmitCrashes(in ParcelFileDescriptor[] fileDescriptors);
+}
diff --git a/android_webview/java/src/org/chromium/android_webview/crash/MinidumpUploadJobService.java b/android_webview/java/src/org/chromium/android_webview/crash/MinidumpUploadJobService.java
new file mode 100644
index 0000000..529ee32
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/crash/MinidumpUploadJobService.java
@@ -0,0 +1,71 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package org.chromium.android_webview.crash;
+
+import android.annotation.TargetApi;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.Build;
+
+import org.chromium.base.ContextUtils;
+
+/**
+ * Class that interacts with the Android JobScheduler to upload Minidumps at appropriate times.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+// OBS: This class needs to be public to be started from android.app.ActivityThread.
+public class MinidumpUploadJobService extends JobService {
+    Object mRunningLock = new Object();
+    boolean mRunningJob = false;
+    MinidumpUploader mMinidumpUploader;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        SynchronizedWebViewCommandLine.initOnSeparateThread();
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        // Ensure we can use ContextUtils later on (from minidump_uploader component).
+        ContextUtils.initApplicationContext(this.getApplicationContext());
+
+        // Ensure we only run one job at a time.
+        synchronized (mRunningLock) {
+            assert !mRunningJob;
+            mRunningJob = true;
+        }
+        mMinidumpUploader = new MinidumpUploaderImpl(this, true /* cleanOutMinidumps */);
+        mMinidumpUploader.uploadAllMinidumps(createJobFinishedCallback(params));
+        return true; // true = processing work on a separate thread, false = done already.
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        boolean reschedule = mMinidumpUploader.cancelUploads();
+        synchronized (mRunningLock) {
+            mRunningJob = false;
+        }
+        return reschedule;
+    }
+
+    private MinidumpUploader.UploadsFinishedCallback createJobFinishedCallback(
+            final JobParameters params) {
+        return new MinidumpUploader.UploadsFinishedCallback() {
+            @Override
+            public void uploadsFinished(boolean reschedule) {
+                synchronized (mRunningLock) {
+                    mRunningJob = false;
+                }
+                MinidumpUploadJobService.this.jobFinished(params, reschedule);
+            }
+        };
+    }
+
+    @Override
+    public void onDestroy() {
+        mMinidumpUploader = null;
+        super.onDestroy();
+    }
+}
diff --git a/android_webview/java/src/org/chromium/android_webview/crash/MinidumpUploader.java b/android_webview/java/src/org/chromium/android_webview/crash/MinidumpUploader.java
new file mode 100644
index 0000000..1957f36a
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/crash/MinidumpUploader.java
@@ -0,0 +1,30 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview.crash;
+
+/**
+ * Interface for uploading minidumps.
+ */
+public interface MinidumpUploader {
+    /**
+     * Try to upload all the minidumps in the crash directory.
+     * This method will be called on the UI thread of our JobService.
+     * @param uploadsFinishedCallback a callback that will be called when the uploading is finished
+     * (whether or not all of the uploads succeeded).
+     */
+    void uploadAllMinidumps(UploadsFinishedCallback uploadsFinishedCallback);
+
+    /**
+     * Cancel the current set of uploads.
+     * @return whether there are still uploads to be done.
+     */
+    boolean cancelUploads();
+
+    /**
+     * Provides an interface for the callback that will be called if all uploads are finished before
+     * they are canceled.
+     */
+    public interface UploadsFinishedCallback { public void uploadsFinished(boolean reschedule); }
+}
diff --git a/android_webview/java/src/org/chromium/android_webview/crash/MinidumpUploaderImpl.java b/android_webview/java/src/org/chromium/android_webview/crash/MinidumpUploaderImpl.java
new file mode 100644
index 0000000..7d9ed32
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/crash/MinidumpUploaderImpl.java
@@ -0,0 +1,187 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview.crash;
+
+import android.content.Context;
+
+import org.chromium.base.Log;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.components.minidump_uploader.CrashFileManager;
+import org.chromium.components.minidump_uploader.MinidumpUploadCallable;
+import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
+
+import java.io.File;
+
+/**
+ * Class in charge of uploading Minidumps from WebView's data directory.
+ * This class gets invoked from a JobScheduler job and posts the operation of uploading minidumps to
+ * a privately defined worker thread.
+ * Note that this implementation is state-less in the sense that it doesn't keep track of whether it
+ * successfully uploaded any minidumps. At the end of a job it simply checks whether there are any
+ * minidumps left to upload, and if so, the job is rescheduled.
+ */
+public class MinidumpUploaderImpl implements MinidumpUploader {
+    private static final String TAG = "MinidumpUploaderImpl";
+    private Thread mWorkerThread;
+    private final CrashFileManager mFileManager;
+
+    private Object mCancelLock = new Object();
+    private boolean mCancelUpload = false;
+
+    private final boolean mCleanOutMinidumps;
+
+    @VisibleForTesting
+    public static final int MAX_UPLOAD_TRIES_ALLOWED = 3;
+
+    /**
+     * Notify the worker thread that the current job has been canceled - so we shouldn't upload any
+     * more minidumps.
+     */
+    private void setCancelUpload(boolean cancel) {
+        synchronized (mCancelLock) {
+            mCancelUpload = cancel;
+        }
+    }
+
+    /**
+     * Check whether the current job has been canceled.
+     */
+    private boolean getCancelUpload() {
+        synchronized (mCancelLock) {
+            return mCancelUpload;
+        }
+    }
+
+    @VisibleForTesting
+    public MinidumpUploaderImpl(Context context, boolean cleanOutMinidumps) {
+        File webviewCrashDir = CrashReceiverService.createWebViewCrashDir(context);
+        mFileManager = new CrashFileManager(webviewCrashDir);
+        if (!mFileManager.ensureCrashDirExists()) {
+            Log.e(TAG, "Crash directory doesn't exist!");
+        }
+        mCleanOutMinidumps = cleanOutMinidumps;
+    }
+
+    /**
+     * Utility method to allow us to test the logic of this class by injecting
+     * test-MinidumpUploadCallables.
+     */
+    @VisibleForTesting
+    public MinidumpUploadCallable createMinidumpUploadCallable(File minidumpFile, File logfile) {
+        return new MinidumpUploadCallable(
+                minidumpFile, logfile, createWebViewCrashReportingManager());
+    }
+
+    private final CrashReportingPermissionManager createWebViewCrashReportingManager() {
+        return new CrashReportingPermissionManager() {
+            @Override
+            public boolean isClientInMetricsSample() {
+                // We will check whether the client is in the metrics sample before
+                // generating a minidump - so if no minidump is generated this code will
+                // never run and we don't need to check whether we are in the sample.
+                // TODO(gsennton): when we switch to using Finch for this value we should use the
+                // Finch value here as well.
+                return true;
+            }
+            @Override
+            public boolean isNetworkAvailableForCrashUploads() {
+                // JobScheduler will call onStopJob causing our upload to be interrupted when our
+                // network requirements no longer hold.
+                return true;
+            }
+            @Override
+            public boolean isCrashUploadDisabledByCommandLine() {
+                return false;
+            }
+            /**
+             * This method is already represented by isClientInMetricsSample() and
+             * isNetworkAvailableForCrashUploads().
+             */
+            @Override
+            public boolean isMetricsUploadPermitted() {
+                return true;
+            }
+            @Override
+            public boolean isUsageAndCrashReportingPermittedByUser() {
+                // TODO(gsennton): make this depend on Android Checkbox when we can read that
+                // through GmsCore.
+                return false;
+            }
+            @Override
+            public boolean isUploadEnabledForTests() {
+                return SynchronizedWebViewCommandLine.hasSwitch(
+                        CrashReceiverService.CRASH_UPLOADS_ENABLED_FOR_TESTING_SWITCH);
+            }
+        };
+    }
+
+    /**
+     * Runnable that upload minidumps.
+     * This is where the actual uploading happens - an upload job consists of posting this Runnable
+     * to the worker thread.
+     */
+    private class UploadRunnable implements Runnable {
+        private final MinidumpUploader.UploadsFinishedCallback mUploadsFinishedCallback;
+
+        public UploadRunnable(MinidumpUploader.UploadsFinishedCallback uploadsFinishedCallback) {
+            mUploadsFinishedCallback = uploadsFinishedCallback;
+        }
+
+        @Override
+        public void run() {
+            File[] minidumps = mFileManager.getAllMinidumpFiles(MAX_UPLOAD_TRIES_ALLOWED);
+            for (File minidump : minidumps) {
+                // Store the name of the current minidump so that we can mark it as a failure from
+                // the main thread if JobScheduler tells us to stop.
+                MinidumpUploadCallable uploadCallable = createMinidumpUploadCallable(
+                        minidump, mFileManager.getCrashUploadLogFile());
+                int uploadResult = uploadCallable.call();
+
+                // Job was canceled -> early out: any clean-up will be performed in cancelUploads().
+                if (getCancelUpload()) return;
+
+                if (uploadResult == MinidumpUploadCallable.UPLOAD_FAILURE) {
+                    String newName = CrashFileManager.tryIncrementAttemptNumber(minidump);
+                    if (newName == null) {
+                        Log.w(TAG, "Failed to increment attempt number of " + minidump);
+                    }
+                }
+            }
+
+            // Clean out old/uploaded minidumps. Note that this clean-up method is more strict than
+            // our copying mechanism in the sense that it keeps less minidumps.
+            if (mCleanOutMinidumps) {
+                mFileManager.cleanOutAllNonFreshMinidumpFiles();
+            }
+
+            // Reschedule if there are still minidumps to upload.
+            boolean reschedule =
+                    mFileManager.getAllMinidumpFiles(MAX_UPLOAD_TRIES_ALLOWED).length > 0;
+            mUploadsFinishedCallback.uploadsFinished(reschedule);
+        }
+    }
+
+    @Override
+    public void uploadAllMinidumps(
+            MinidumpUploader.UploadsFinishedCallback uploadsFinishedCallback) {
+        if (mWorkerThread != null) {
+            throw new RuntimeException("Only one upload-job should be active at a time");
+        }
+        mWorkerThread = new Thread(new UploadRunnable(uploadsFinishedCallback), "mWorkerThread");
+        setCancelUpload(false);
+        mWorkerThread.start();
+    }
+
+    /**
+     * @return whether to reschedule the uploads.
+     */
+    @Override
+    public boolean cancelUploads() {
+        setCancelUpload(true);
+
+        // Reschedule if there are still minidumps to upload.
+        return mFileManager.getAllMinidumpFiles(MAX_UPLOAD_TRIES_ALLOWED).length > 0;
+    }
+}
diff --git a/android_webview/java/src/org/chromium/android_webview/crash/SynchronizedWebViewCommandLine.java b/android_webview/java/src/org/chromium/android_webview/crash/SynchronizedWebViewCommandLine.java
new file mode 100644
index 0000000..27331a89
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/crash/SynchronizedWebViewCommandLine.java
@@ -0,0 +1,59 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview.crash;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.annotations.SuppressFBWarnings;
+
+/**
+ * Class for fetching command line switches for WebView in a thread-safe way.
+ */
+class SynchronizedWebViewCommandLine {
+    private static final Object sLock = new Object();
+    private static InitState sInitialized = InitState.NOT_STARTED;
+    // TODO(gsennton): this value is used in WebViewChromiumFactoryProvider as well - set it
+    // somewhere where it can be read from both classes.
+    private static final String WEBVIEW_COMMAND_LINE_FILE = "/data/local/tmp/webview-command-line";
+
+    private enum InitState { NOT_STARTED, STARTED, DONE }
+
+    /**
+     * Initialize the global CommandLine using the WebView command line file.
+     * This method includes IO operations - it shouldn't be performed on the main thread.
+     */
+    public static void initOnSeparateThread() {
+        synchronized (sLock) {
+            if (sInitialized != InitState.NOT_STARTED) return;
+            sInitialized = InitState.STARTED;
+        }
+        new Thread(new Runnable() {
+            @SuppressFBWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME")
+            @Override
+            public void run() {
+                CommandLine.initFromFile(WEBVIEW_COMMAND_LINE_FILE);
+                synchronized (sLock) {
+                    sInitialized = InitState.DONE;
+                    sLock.notifyAll();
+                }
+            }
+        }, "WebView-command-line-init-thread").start();
+    }
+
+    /**
+     * Returns true if this command line contains the given switch.
+     */
+    public static boolean hasSwitch(String switchString) {
+        synchronized (sLock) {
+            while (sInitialized != InitState.DONE) {
+                try {
+                    sLock.wait();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            return CommandLine.getInstance().hasSwitch(switchString);
+        }
+    }
+}
diff --git a/android_webview/javatests/DEPS b/android_webview/javatests/DEPS
index 091f88d..17aa9d6 100644
--- a/android_webview/javatests/DEPS
+++ b/android_webview/javatests/DEPS
@@ -1,4 +1,6 @@
 include_rules = [
+  "+components/minidump_uploader/android/java",
+  "+components/minidump_uploader/android/javatests",
   "+components/policy/android/java",
   "+components/policy/android/javatests",
   "+content/public/android/java",
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/crash/MinidumpUploaderTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/crash/MinidumpUploaderTest.java
new file mode 100644
index 0000000..0f39c4c
--- /dev/null
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/crash/MinidumpUploaderTest.java
@@ -0,0 +1,479 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview.crash.test;
+
+import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import org.chromium.android_webview.crash.CrashReceiverService;
+import org.chromium.android_webview.crash.MinidumpUploader;
+import org.chromium.android_webview.crash.MinidumpUploaderImpl;
+import org.chromium.base.FileUtils;
+import org.chromium.components.minidump_uploader.CrashFileManager;
+import org.chromium.components.minidump_uploader.CrashTestCase;
+import org.chromium.components.minidump_uploader.MinidumpUploadCallable;
+import org.chromium.components.minidump_uploader.MinidumpUploadCallableTest;
+import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
+import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Instrumentation tests for MinidumpUploader.
+ */
+public class MinidumpUploaderTest extends CrashTestCase {
+    private static final String TAG = "MinidumpUploaderTest";
+    private static final String BOUNDARY = "TESTBOUNDARY";
+
+    private static final long TIME_OUT_MILLIS = 3000;
+
+    @Override
+    protected File getExistingCacheDir() {
+        return CrashReceiverService.createWebViewCrashDir(getInstrumentation().getTargetContext());
+    }
+
+    /**
+     * Test to ensure the minidump uploading mechanism behaves as expected when we fail to upload
+     * minidumps.
+     */
+    @MediumTest
+    public void testFailUploadingMinidumps() throws IOException {
+        final CrashReportingPermissionManager permManager =
+                new MockCrashReportingPermissionManager() {
+                    {
+                        mIsInSample = true;
+                        mIsPermitted = true;
+                        mIsUserPermitted = true;
+                        mIsCommandLineDisabled = false;
+                        mIsNetworkAvailable = false; // Will cause us to fail uploads
+                        mIsEnabledForTests = false;
+                    }
+                };
+        MinidumpUploader minidumpUploader = new MinidumpUploaderImpl(
+                getInstrumentation().getTargetContext(), false /* cleanOutMinidumps */) {
+            @Override
+            public MinidumpUploadCallable createMinidumpUploadCallable(
+                    File minidumpFile, File logfile) {
+                return new MinidumpUploadCallable(minidumpFile, logfile,
+                        new MinidumpUploadCallableTest.TestHttpURLConnectionFactory(), permManager);
+            }
+        };
+
+        File firstFile = createMinidumpFileInCrashDir("1_abc.dmp0");
+        File secondFile = createMinidumpFileInCrashDir("12_abc.dmp0");
+        String triesBelowMaxString = ".try" + (MinidumpUploaderImpl.MAX_UPLOAD_TRIES_ALLOWED - 1);
+        String maxTriesString = ".try" + MinidumpUploaderImpl.MAX_UPLOAD_TRIES_ALLOWED;
+        File justBelowMaxTriesFile =
+                createMinidumpFileInCrashDir("belowmaxtries.dmp0" + triesBelowMaxString);
+        File maxTriesFile = createMinidumpFileInCrashDir("maxtries.dmp0" + maxTriesString);
+
+        File expectedFirstFile = new File(mCrashDir, firstFile.getName() + ".try1");
+        File expectedSecondFile = new File(mCrashDir, secondFile.getName() + ".try1");
+        File expectedJustBelowMaxTriesFile = new File(mCrashDir,
+                justBelowMaxTriesFile.getName().replace(triesBelowMaxString, maxTriesString));
+
+        uploadMinidumpsSync(minidumpUploader, true /* expectReschedule */);
+        assertFalse(firstFile.exists());
+        assertFalse(secondFile.exists());
+        assertFalse(justBelowMaxTriesFile.exists());
+        assertTrue(expectedFirstFile.exists());
+        assertTrue(expectedSecondFile.exists());
+        assertTrue(expectedJustBelowMaxTriesFile.exists());
+        // This file should have been left untouched.
+        assertTrue(maxTriesFile.exists());
+    }
+
+    private static void uploadMinidumpsSync(
+            MinidumpUploader minidumpUploader, final boolean expectReschedule) {
+        final CountDownLatch uploadsFinishedLatch = new CountDownLatch(1);
+        minidumpUploader.uploadAllMinidumps(new MinidumpUploader.UploadsFinishedCallback() {
+            @Override
+            public void uploadsFinished(boolean reschedule) {
+                assertEquals(expectReschedule, reschedule);
+                uploadsFinishedLatch.countDown();
+            }
+        });
+        try {
+            assertTrue(uploadsFinishedLatch.await(
+                    scaleTimeout(TIME_OUT_MILLIS), TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Ensure MinidumpUploaderImpl doesn't crash even if the WebView Crash dir doesn't exist (could
+     * happen e.g. if a Job persists across WebView-updates?
+     */
+    @MediumTest
+    public void testUploadingWithoutCrashDir() throws IOException {
+        File webviewCrashDir = getExistingCacheDir();
+        // Delete the WebView crash directory to ensure MinidumpUploader doesn't crash without it.
+        FileUtils.recursivelyDeleteFile(webviewCrashDir);
+        assertFalse(webviewCrashDir.exists());
+
+        final CrashReportingPermissionManager permManager =
+                new MockCrashReportingPermissionManager() {
+                    { mIsEnabledForTests = true; }
+                };
+        MinidumpUploader minidumpUploader = new MinidumpUploaderImpl(
+                getInstrumentation().getTargetContext(), false /* cleanOutMinidumps */) {
+            @Override
+            public MinidumpUploadCallable createMinidumpUploadCallable(
+                    File minidumpFile, File logfile) {
+                return new MinidumpUploadCallable(minidumpFile, logfile,
+                        new MinidumpUploadCallableTest.TestHttpURLConnectionFactory(), permManager);
+            }
+        };
+
+        File firstFile = createMinidumpFileInCrashDir("1_abc.dmp0");
+        File secondFile = createMinidumpFileInCrashDir("12_abcd.dmp0");
+        File expectedFirstUploadFile =
+                new File(mCrashDir, firstFile.getName().replace(".dmp", ".up"));
+        File expectedSecondUploadFile =
+                new File(mCrashDir, secondFile.getName().replace(".dmp", ".up"));
+
+        uploadMinidumpsSync(minidumpUploader, false /* expectReschedule */);
+
+        assertFalse(firstFile.exists());
+        assertTrue(expectedFirstUploadFile.exists());
+        assertFalse(secondFile.exists());
+        assertTrue(expectedSecondUploadFile.exists());
+    }
+
+    private interface MinidumpUploadCallableCreator {
+        MinidumpUploadCallable createCallable(File minidumpFile, File logfile);
+    }
+
+    private static MinidumpUploaderImpl createCallableListMinidumpUploader(
+            Context context, final List<MinidumpUploadCallableCreator> callables) {
+        return new MinidumpUploaderImpl(context, false /* cleanOutMinidumps */) {
+            private int mIndex = 0;
+
+            @Override
+            public MinidumpUploadCallable createMinidumpUploadCallable(
+                    File minidumpFile, File logfile) {
+                if (mIndex >= callables.size()) {
+                    fail("Should not create callable number " + mIndex);
+                }
+                return callables.get(mIndex++).createCallable(minidumpFile, logfile);
+            }
+        };
+    }
+
+    @MediumTest
+    public void testFailingThenPassingUpload() throws IOException {
+        final CrashReportingPermissionManager permManager =
+                new MockCrashReportingPermissionManager() {
+                    { mIsEnabledForTests = true; }
+                };
+        List<MinidumpUploadCallableCreator> callables = new ArrayList();
+        callables.add(new MinidumpUploadCallableCreator() {
+            @Override
+            public MinidumpUploadCallable createCallable(File minidumpFile, File logfile) {
+                return new MinidumpUploadCallable(
+                        minidumpFile, logfile, new FailingHttpUrlConnectionFactory(), permManager);
+            }
+        });
+        callables.add(new MinidumpUploadCallableCreator() {
+            @Override
+            public MinidumpUploadCallable createCallable(File minidumpFile, File logfile) {
+                return new MinidumpUploadCallable(minidumpFile, logfile,
+                        new MinidumpUploadCallableTest.TestHttpURLConnectionFactory(), permManager);
+            }
+        });
+        MinidumpUploader minidumpUploader = createCallableListMinidumpUploader(
+                getInstrumentation().getTargetContext(), callables);
+
+        File firstFile = createMinidumpFileInCrashDir("firstFile.dmp0");
+        File secondFile = createMinidumpFileInCrashDir("secondFile.dmp0");
+
+        uploadMinidumpsSync(minidumpUploader, true /* expectReschedule */);
+        assertFalse(firstFile.exists());
+        assertFalse(secondFile.exists());
+        File expectedSecondFile = null;
+        // Not sure which minidump will fail and which will succeed, so just ensure one was uploaded
+        // and the other one failed.
+        if (new File(mCrashDir, firstFile.getName() + ".try1").exists()) {
+            expectedSecondFile = new File(mCrashDir, secondFile.getName().replace(".dmp", ".up"));
+        } else {
+            File uploadedFirstFile =
+                    new File(mCrashDir, firstFile.getName().replace(".dmp", ".up"));
+            assertTrue(uploadedFirstFile.exists());
+            expectedSecondFile = new File(mCrashDir, secondFile.getName() + ".try1");
+        }
+        assertTrue(expectedSecondFile.exists());
+    }
+
+    private static class StallingOutputStream extends OutputStream {
+        @Override
+        public void write(int b) throws IOException {
+            try {
+                TimeUnit.MINUTES.sleep(100);
+            } catch (InterruptedException e) {
+                throw new InterruptedIOException(e.toString());
+            }
+        }
+    }
+
+    private static class StallingHttpUrlConnectionFactory implements HttpURLConnectionFactory {
+        public HttpURLConnection createHttpURLConnection(String url) {
+            return new HttpURLConnection(null) {
+                @Override
+                public OutputStream getOutputStream() throws IOException {
+                    return new StallingOutputStream();
+                }
+
+                @Override
+                public void connect() {}
+
+                @Override
+                public void disconnect() {}
+
+                @Override
+                public boolean usingProxy() {
+                    return false;
+                }
+            };
+        }
+    }
+
+    private static class FailingHttpUrlConnectionFactory implements HttpURLConnectionFactory {
+        public HttpURLConnection createHttpURLConnection(String url) {
+            return null;
+        }
+    }
+
+    /**
+     * Test that ensure we can interrupt the MinidumpUploader when uploading minidumps.
+     */
+    @MediumTest
+    public void testCancelMinidumpUploads() throws IOException {
+        final CrashReportingPermissionManager permManager =
+                new MockCrashReportingPermissionManager() {
+                    { mIsEnabledForTests = true; }
+                };
+        MinidumpUploader minidumpUploader = new MinidumpUploaderImpl(
+                getInstrumentation().getTargetContext(), false /* cleanOutMinidumps */) {
+            @Override
+            public MinidumpUploadCallable createMinidumpUploadCallable(
+                    File minidumpFile, File logfile) {
+                return new MinidumpUploadCallable(
+                        minidumpFile, logfile, new StallingHttpUrlConnectionFactory(), permManager);
+            }
+        };
+
+        File firstFile = createMinidumpFileInCrashDir("123_abc.dmp0");
+        File nonExpectedFirstUploadFile = new File(mCrashDir, firstFile.getName() + ".up");
+        File nonExpectedFirstRetryFile = new File(mCrashDir, firstFile.getName() + ".try1");
+
+        minidumpUploader.uploadAllMinidumps(new MinidumpUploader.UploadsFinishedCallback() {
+            @Override
+            public void uploadsFinished(boolean reschedule) {
+                fail("This method shouldn't be called when we interrupt uploads.");
+            }
+        });
+        minidumpUploader.cancelUploads();
+
+        assertTrue(firstFile.exists());
+        assertFalse(nonExpectedFirstUploadFile.exists());
+        assertFalse(nonExpectedFirstRetryFile.exists());
+    }
+
+    /**
+     * Ensures that the minidump copying works together with the minidump uploading.
+     */
+    @MediumTest
+    public void testCopyAndUploadWebViewMinidump() throws FileNotFoundException, IOException {
+        final CrashFileManager fileManager = new CrashFileManager(
+                CrashReceiverService.getWebViewCrashDir(getInstrumentation().getTargetContext()));
+        // Note that these minidump files are set up directly in the cache dir - not in the WebView
+        // crash dir. This is to ensure the CrashFileManager doesn't see these minidumps without us
+        // first copying them.
+        File minidumpToCopy =
+                new File(getInstrumentation().getTargetContext().getCacheDir(), "toCopy.dmp");
+        setUpMinidumpFile(minidumpToCopy, BOUNDARY, "browser");
+        final String expectedFileContent = readEntireFile(minidumpToCopy);
+
+        File[] uploadedFiles = copyAndUploadMinidumpsSync(
+                fileManager, new File[][] {{minidumpToCopy}}, new int[] {0});
+
+        // CrashReceiverService will rename the minidumps to some globally unique file name
+        // meaning that we have to check the contents of the minidump rather than the file
+        // name.
+        try {
+            assertEquals(expectedFileContent, readEntireFile(uploadedFiles[0]));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        File webviewTmpDir =
+                CrashReceiverService.getWebViewTmpCrashDir(getInstrumentation().getTargetContext());
+        assertEquals(0, webviewTmpDir.listFiles().length);
+    }
+
+    private static String readEntireFile(File file) throws IOException {
+        FileInputStream fileInputStream = new FileInputStream(file);
+        try {
+            byte[] data = new byte[(int) file.length()];
+            fileInputStream.read(data);
+            return new String(data);
+        } finally {
+            fileInputStream.close();
+        }
+    }
+
+    /**
+     * Ensure that the minidump copying doesn't trigger when we pass it invalid file descriptors.
+     */
+    @MediumTest
+    public void testCopyingAbortsForInvalidFds() throws FileNotFoundException, IOException {
+        assertFalse(CrashReceiverService.copyMinidumps(
+                getInstrumentation().getTargetContext(), 0 /* uid */, null));
+        assertFalse(CrashReceiverService.copyMinidumps(getInstrumentation().getTargetContext(),
+                0 /* uid */, new ParcelFileDescriptor[] {null, null}));
+        assertFalse(CrashReceiverService.copyMinidumps(
+                getInstrumentation().getTargetContext(), 0 /* uid */, new ParcelFileDescriptor[0]));
+    }
+
+    /**
+     * Ensure deleting temporary files used when copying minidumps works correctly.
+     */
+    @MediumTest
+    public void testDeleteFilesInDir() throws IOException {
+        Context context = getInstrumentation().getTargetContext();
+        File webviewTmpDir = CrashReceiverService.getWebViewTmpCrashDir(context);
+        if (!webviewTmpDir.isDirectory()) {
+            assertTrue(webviewTmpDir.mkdir());
+        }
+        File testFile1 = new File(webviewTmpDir, "testFile1");
+        File testFile2 = new File(webviewTmpDir, "testFile2");
+        assertTrue(testFile1.createNewFile());
+        assertTrue(testFile2.createNewFile());
+        assertTrue(testFile1.exists());
+        assertTrue(testFile2.exists());
+        CrashReceiverService.deleteFilesInWebViewTmpDirIfExists(context);
+        assertFalse(testFile1.exists());
+        assertFalse(testFile2.exists());
+    }
+
+    /**
+     * Ensure we can copy and upload several batches of files (i.e. emulate several copying-calls in
+     * a row without the copying-service being destroyed in between).
+     */
+    @MediumTest
+    public void testCopyAndUploadSeveralMinidumpBatches() throws IOException {
+        final CrashFileManager fileManager = new CrashFileManager(
+                CrashReceiverService.getWebViewCrashDir(getInstrumentation().getTargetContext()));
+        // Note that these minidump files are set up directly in the cache dir - not in the WebView
+        // crash dir. This is to ensure the CrashFileManager doesn't see these minidumps without us
+        // first copying them.
+        File firstMinidumpToCopy =
+                new File(getInstrumentation().getTargetContext().getCacheDir(), "firstToCopy.dmp");
+        File secondMinidumpToCopy =
+                new File(getInstrumentation().getTargetContext().getCacheDir(), "secondToCopy.dmp");
+        setUpMinidumpFile(firstMinidumpToCopy, BOUNDARY, "browser");
+        setUpMinidumpFile(secondMinidumpToCopy, BOUNDARY, "renderer");
+        final String expectedFirstFileContent = readEntireFile(firstMinidumpToCopy);
+        final String expectedSecondFileContent = readEntireFile(secondMinidumpToCopy);
+
+        File[] uploadedFiles = copyAndUploadMinidumpsSync(fileManager,
+                new File[][] {{firstMinidumpToCopy}, {secondMinidumpToCopy}}, new int[] {0, 0});
+
+        // CrashReceiverService will rename the minidumps to some globally unique file name
+        // meaning that we have to check the contents of the minidump rather than the file
+        // name.
+        try {
+            final String actualFileContent0 = readEntireFile(uploadedFiles[0]);
+            final String actualFileContent1 = readEntireFile(uploadedFiles[1]);
+            if (expectedFirstFileContent.equals(actualFileContent0)) {
+                assertEquals(expectedSecondFileContent, actualFileContent1);
+            } else {
+                assertEquals(expectedFirstFileContent, actualFileContent1);
+                assertEquals(expectedSecondFileContent, actualFileContent0);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Copy and upload {@param minidumps} by one array at a time - i.e. the minidumps in a single
+     * array in {@param minidumps} will all be copied in the same call into CrashReceiverService.
+     * @param fileManager the CrashFileManager to use when copying/renaming minidumps.
+     * @param minidumps an array of arrays of minidumps to copy and upload, by copying one array at
+     * a time.
+     * @param uids an array of uids declaring the uids used when calling into CrashReceiverService.
+     * @return the uploaded files.
+     */
+    private File[] copyAndUploadMinidumpsSync(CrashFileManager fileManager, File[][] minidumps,
+            int[] uids) throws FileNotFoundException {
+        CrashReceiverService crashReceiverService = new CrashReceiverService();
+        assertEquals(minidumps.length, uids.length);
+        // Ensure the upload service minidump directory is empty before we start copying files.
+        File[] initialMinidumps =
+                fileManager.getAllMinidumpFiles(MinidumpUploaderImpl.MAX_UPLOAD_TRIES_ALLOWED);
+        assertEquals(0, initialMinidumps.length);
+
+        // Open file descriptors to the files and then delete the files.
+        ParcelFileDescriptor[][] fileDescriptors = new ParcelFileDescriptor[minidumps.length][];
+        int numMinidumps = 0;
+        for (int n = 0; n < minidumps.length; n++) {
+            File[] currentMinidumps = minidumps[n];
+            numMinidumps += currentMinidumps.length;
+            fileDescriptors[n] = new ParcelFileDescriptor[currentMinidumps.length];
+            for (int m = 0; m < currentMinidumps.length; m++) {
+                fileDescriptors[n][m] = ParcelFileDescriptor.open(
+                        currentMinidumps[m], ParcelFileDescriptor.MODE_READ_ONLY);
+                assertTrue(currentMinidumps[m].delete());
+            }
+            crashReceiverService.performMinidumpCopyingSerially(
+                    getInstrumentation().getTargetContext(), uids[n] /* uid */, fileDescriptors[n],
+                    false /* scheduleUploads */);
+        }
+
+        final CrashReportingPermissionManager permManager =
+                new MockCrashReportingPermissionManager() {
+                    { mIsEnabledForTests = true; }
+                };
+        MinidumpUploader minidumpUploader = new MinidumpUploaderImpl(
+                getInstrumentation().getTargetContext(), false /* cleanOutMinidumps */) {
+            @Override
+            public MinidumpUploadCallable createMinidumpUploadCallable(
+                    File minidumpFile, File logfile) {
+                return new MinidumpUploadCallable(minidumpFile, logfile,
+                        new MinidumpUploadCallableTest.TestHttpURLConnectionFactory(), permManager);
+            }
+        };
+
+        uploadMinidumpsSync(minidumpUploader, false /* expectReschedule */);
+        // Ensure there are no minidumps left to upload.
+        File[] nonUploadedMinidumps =
+                fileManager.getAllMinidumpFiles(MinidumpUploaderImpl.MAX_UPLOAD_TRIES_ALLOWED);
+        assertEquals(0, nonUploadedMinidumps.length);
+
+        File[] uploadedFiles = fileManager.getAllUploadedFiles();
+        assertEquals(numMinidumps, uploadedFiles.length);
+        return uploadedFiles;
+    }
+
+    private File createMinidumpFileInCrashDir(String name) throws IOException {
+        File minidumpFile = new File(mCrashDir, name);
+        setUpMinidumpFile(minidumpFile, BOUNDARY);
+        return minidumpFile;
+    }
+}
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 5e82e2b..c4c8a09 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -101,9 +101,12 @@
   apk_under_test = ":android_webview_apk"
   android_manifest = "../javatests/AndroidManifest.xml"
   deps = [
+    "//android_webview:android_webview_crash_services_java",
     "//android_webview:android_webview_java",
     "//base:base_java",
     "//base:base_java_test_support",
+    "//components/minidump_uploader:minidump_uploader_java",
+    "//components/minidump_uploader:minidump_uploader_javatests",
     "//components/policy/android:policy_java",
     "//components/policy/android:policy_java_test_support",
     "//components/web_contents_delegate_android:web_contents_delegate_android_java",
@@ -191,6 +194,7 @@
     "../javatests/src/org/chromium/android_webview/test/WebViewAsynchronousFindApisTest.java",
     "../javatests/src/org/chromium/android_webview/test/WebViewFindApisTestBase.java",
     "../javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java",
+    "../javatests/src/org/chromium/android_webview/test/crash/MinidumpUploaderTest.java",
     "../javatests/src/org/chromium/android_webview/test/util/AwQuotaManagerBridgeTestUtil.java",
     "../javatests/src/org/chromium/android_webview/test/util/AwTestTouchUtils.java",
     "../javatests/src/org/chromium/android_webview/test/util/CommonResources.java",
diff --git a/android_webview/tools/apk_merger.py b/android_webview/tools/apk_merger.py
index 74eac37..713a58d 100755
--- a/android_webview/tools/apk_merger.py
+++ b/android_webview/tools/apk_merger.py
@@ -135,8 +135,7 @@
 
 
 def SignAndAlignApk(tmp_apk, signed_tmp_apk, new_apk, zipalign_path,
-                    keystore_path, key_name, key_password,
-                    page_align_shared_libraries):
+                    keystore_path, key_name, key_password):
   try:
     finalize_apk.JarSigner(
         keystore_path,
@@ -149,7 +148,6 @@
 
   try:
     finalize_apk.AlignApk(zipalign_path,
-                          page_align_shared_libraries,
                           signed_tmp_apk,
                           new_apk)
   except build_utils.CalledProcessError as e:
@@ -224,7 +222,8 @@
   parser.add_argument('--key_name', required=True)
   parser.add_argument('--key_password', required=True)
   parser.add_argument('--shared_library')
-  parser.add_argument('--page-align-shared-libraries', action='store_true')
+  parser.add_argument('--page-align-shared-libraries', action='store_true',
+                      help='Obsolete, but remains for backwards compatibility')
   parser.add_argument('--uncompress-shared-libraries', action='store_true')
   parser.add_argument('--debug', action='store_true')
   # This option shall only used in debug build, see http://crbug.com/631494.
@@ -250,8 +249,7 @@
     MergeApk(args, tmp_apk, tmp_dir_32, tmp_dir_64)
 
     SignAndAlignApk(tmp_apk, signed_tmp_apk, new_apk, args.zipalign_path,
-                    args.keystore_path, args.key_name, args.key_password,
-                    args.page_align_shared_libraries)
+                    args.keystore_path, args.key_name, args.key_password)
 
   except ApkMergeFailure as e:
     print e
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 2e93167..3ddf11a 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -890,6 +890,7 @@
     "//device/bluetooth",
     "//media",
     "//net",
+    "//services/preferences/public/cpp",
     "//services/service_manager/public/cpp",
     "//services/ui/public/interfaces",
     "//skia",
diff --git a/ash/common/DEPS b/ash/common/DEPS
index ed0a9ac3..f127b5e0b 100644
--- a/ash/common/DEPS
+++ b/ash/common/DEPS
@@ -4,8 +4,10 @@
   "+ash/public",
   "+ash/resources",
   "+ash/shared",
+  "+components/prefs",
   "+components/ui_devtools",
   "+mojo/public/cpp",
+  "+services/preferences/public",
   "+ui",
 ]
 
diff --git a/ash/common/wm_shell.cc b/ash/common/wm_shell.cc
index 0bd31c797..5e80f334 100644
--- a/ash/common/wm_shell.cc
+++ b/ash/common/wm_shell.cc
@@ -48,6 +48,9 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
+#include "services/preferences/public/cpp/pref_observer_store.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "ui/app_list/presenter/app_list.h"
 #include "ui/app_list/presenter/app_list_presenter.h"
 #include "ui/display/display.h"
@@ -122,6 +125,9 @@
 
   // Balances the Install() in Initialize().
   views::FocusManagerFactory::Install(nullptr);
+
+  // Removes itself as an observer of |pref_store_|.
+  shelf_controller_.reset();
 }
 
 ShelfModel* WmShell::shelf_model() {
@@ -268,6 +274,14 @@
   vpn_list_ = base::MakeUnique<VpnList>();
 #endif
   session_controller_->AddSessionStateObserver(this);
+
+  prefs::mojom::PreferencesManagerPtr pref_manager_ptr;
+  // Can be null in tests.
+  if (!delegate_->GetShellConnector())
+    return;
+  delegate_->GetShellConnector()->ConnectToInterface(prefs::mojom::kServiceName,
+                                                     &pref_manager_ptr);
+  pref_store_ = new preferences::PrefObserverStore(std::move(pref_manager_ptr));
 }
 
 WmShell::~WmShell() {
diff --git a/ash/common/wm_shell.h b/ash/common/wm_shell.h
index a4ecd2c0..45e4f5c8 100644
--- a/ash/common/wm_shell.h
+++ b/ash/common/wm_shell.h
@@ -39,6 +39,10 @@
 class Point;
 }
 
+namespace preferences {
+class PrefObserverStore;
+}
+
 namespace views {
 class PointerWatcher;
 enum class PointerWatcherEventTypes;
@@ -161,6 +165,8 @@
 
   PaletteDelegate* palette_delegate() { return palette_delegate_.get(); }
 
+  preferences::PrefObserverStore* pref_store() { return pref_store_.get(); }
+
   SessionController* session_controller() { return session_controller_.get(); }
 
   ShelfController* shelf_controller() { return shelf_controller_.get(); }
@@ -486,6 +492,8 @@
   base::ObserverList<ShellObserver> shell_observers_;
   std::unique_ptr<ShellDelegate> delegate_;
 
+  scoped_refptr<preferences::PrefObserverStore> pref_store_;
+
   std::unique_ptr<AcceleratorController> accelerator_controller_;
   std::unique_ptr<AccessibilityDelegate> accessibility_delegate_;
   std::unique_ptr<app_list::AppList> app_list_;
diff --git a/ash/mus/manifest.json b/ash/mus/manifest.json
index c52e761..29ca609 100644
--- a/ash/mus/manifest.json
+++ b/ash/mus/manifest.json
@@ -26,6 +26,7 @@
       "requires": {
         "*": [ "accessibility", "app" ],
         "content_browser": [ "ash" ],
+        "preferences": [ "preferences_manager" ],
         "ui": [ "window_manager" ],
         "touch_hud": [ "mash:launchable" ]
       }
diff --git a/base/power_monitor/power_monitor_device_source.cc b/base/power_monitor/power_monitor_device_source.cc
index d7060c2..5df58003 100644
--- a/base/power_monitor/power_monitor_device_source.cc
+++ b/base/power_monitor/power_monitor_device_source.cc
@@ -4,13 +4,9 @@
 
 #include "base/power_monitor/power_monitor_device_source.h"
 
-#include "base/threading/thread_task_runner_handle.h"
-
 namespace base {
 
 PowerMonitorDeviceSource::PowerMonitorDeviceSource() {
-  DCHECK(ThreadTaskRunnerHandle::IsSet());
-
 #if defined(OS_MACOSX)
   PlatformInit();
 #endif
diff --git a/base/power_monitor/power_monitor_device_source.h b/base/power_monitor/power_monitor_device_source.h
index 69237cd..1e2c885 100644
--- a/base/power_monitor/power_monitor_device_source.h
+++ b/base/power_monitor/power_monitor_device_source.h
@@ -7,8 +7,6 @@
 
 #include "base/base_export.h"
 #include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/observer_list_threadsafe.h"
 #include "base/power_monitor/power_monitor_source.h"
 #include "base/power_monitor/power_observer.h"
 #include "build/build_config.h"
diff --git a/base/task_scheduler/task_traits.h b/base/task_scheduler/task_traits.h
index d2694225..26937b91 100644
--- a/base/task_scheduler/task_traits.h
+++ b/base/task_scheduler/task_traits.h
@@ -100,8 +100,7 @@
   TaskTraits& MayBlock();
 
   // Tasks with this trait are allowed to wait on waitable events and condition
-  // variables as well as to join threads and processes. This trait implies
-  // MayBlock().
+  // variables as well as to join threads and processes.
   //
   // This trait should generally not be used.
   //
@@ -117,6 +116,10 @@
   // base::Create(Sequenced|SingleTreaded)TaskRunnerWithTraits(). If a thread is
   // really needed, make it non-joinable and add cleanup work at the end of the
   // thread's main function (if using base::Thread, override Cleanup()).
+  //
+  // MayBlock() must be specified in conjunction with this trait if and only if
+  // removing usage of sync primitives in the labeled tasks would still result
+  // in tasks that may block (per MayBlock()'s definition).
   TaskTraits& WithSyncPrimitives();
 
   // DEPRECATED
diff --git a/build/android/gradle/build.gradle.jinja b/build/android/gradle/build.gradle.jinja
index ee3317ee..008a1d9 100644
--- a/build/android/gradle/build.gradle.jinja
+++ b/build/android/gradle/build.gradle.jinja
@@ -9,7 +9,7 @@
         jcenter()
     }
     dependencies {
-        classpath "com.android.tools.build:gradle:2.2.1"
+        classpath "com.android.tools.build:gradle:2.2.3"
     }
 }
 
diff --git a/build/android/gyp/apkbuilder.py b/build/android/gyp/apkbuilder.py
index 82ac496ed..5d87625 100755
--- a/build/android/gyp/apkbuilder.py
+++ b/build/android/gyp/apkbuilder.py
@@ -163,12 +163,15 @@
   """Add native libraries to APK."""
   for path in native_libs:
     basename = os.path.basename(path)
-    apk_path = 'lib/%s/%s' % (android_abi, basename)
 
     compress = None
-    if (uncompress and os.path.splitext(basename)[1] == '.so'):
+    if (uncompress and os.path.splitext(basename)[1] == '.so'
+        and 'android_linker' not in basename):
       compress = False
+      # Add prefix to prevent android install from extracting upon install.
+      basename = 'crazy.' + basename
 
+    apk_path = 'lib/%s/%s' % (android_abi, basename)
     build_utils.AddToZipHermetic(out_apk,
                                  apk_path,
                                  src_path=path,
@@ -265,8 +268,9 @@
                               options.uncompress_shared_libraries)
 
         for name in sorted(options.native_lib_placeholders):
-          # Empty libs files are ignored by md5check, but rezip requires them
-          # to be empty in order to identify them as placeholders.
+          # Note: Empty libs files are ignored by md5check (can cause issues
+          # with stale builds when the only change is adding/removing
+          # placeholders).
           apk_path = 'lib/%s/%s' % (options.android_abi, name)
           build_utils.AddToZipHermetic(out_apk, apk_path, data='')
 
diff --git a/build/android/gyp/finalize_apk.py b/build/android/gyp/finalize_apk.py
index 532d001f..ecb5ebfe 100755
--- a/build/android/gyp/finalize_apk.py
+++ b/build/android/gyp/finalize_apk.py
@@ -21,32 +21,6 @@
 
 from util import build_utils
 
-def RenameInflateAndAddPageAlignment(
-    rezip_apk_jar_path, in_zip_file, out_zip_file):
-  rezip_apk_cmd = [
-      'java',
-      '-classpath',
-      rezip_apk_jar_path,
-      'RezipApk',
-      'renamealign',
-      in_zip_file,
-      out_zip_file,
-    ]
-  build_utils.CheckOutput(rezip_apk_cmd)
-
-
-def ReorderAndAlignApk(rezip_apk_jar_path, in_zip_file, out_zip_file):
-  rezip_apk_cmd = [
-      'java',
-      '-classpath',
-      rezip_apk_jar_path,
-      'RezipApk',
-      'reorder',
-      in_zip_file,
-      out_zip_file,
-    ]
-  build_utils.CheckOutput(rezip_apk_cmd)
-
 
 def JarSigner(key_path, key_name, key_passwd, unsigned_path, signed_path):
   shutil.copy(unsigned_path, signed_path)
@@ -62,14 +36,15 @@
   build_utils.CheckOutput(sign_cmd)
 
 
-def AlignApk(zipalign_path, package_align, unaligned_path, final_path):
+def AlignApk(zipalign_path, unaligned_path, final_path):
+  # Note -p will page align native libraries (files ending with .so), but
+  # only those that are stored uncompressed.
   align_cmd = [
       zipalign_path,
-      '-f'
+      '-p',
+      '-f',
       ]
 
-  if package_align:
-    align_cmd += ['-p']
 
   align_cmd += [
       '4',  # 4 bytes
@@ -85,23 +60,13 @@
   parser = optparse.OptionParser()
   build_utils.AddDepfileOption(parser)
 
-  parser.add_option('--rezip-apk-jar-path',
-                    help='Path to the RezipApk jar file.')
   parser.add_option('--zipalign-path', help='Path to the zipalign tool.')
-  parser.add_option('--page-align-shared-libraries',
-                    action='store_true',
-                    help='Page align shared libraries.')
   parser.add_option('--unsigned-apk-path', help='Path to input unsigned APK.')
   parser.add_option('--final-apk-path',
       help='Path to output signed and aligned APK.')
   parser.add_option('--key-path', help='Path to keystore for signing.')
   parser.add_option('--key-passwd', help='Keystore password')
   parser.add_option('--key-name', help='Keystore name')
-  parser.add_option('--stamp', help='Path to touch on success.')
-  parser.add_option('--load-library-from-zip', type='int',
-      help='If non-zero, build the APK such that the library can be loaded ' +
-           'directly from the zip file using the crazy linker. The library ' +
-           'will be renamed, uncompressed and page aligned.')
 
   options, _ = parser.parse_args()
 
@@ -110,14 +75,9 @@
     options.key_path,
   ]
 
-  if options.load_library_from_zip:
-    input_paths.append(options.rezip_apk_jar_path)
-
   input_strings = [
-    options.load_library_from_zip,
     options.key_name,
     options.key_passwd,
-    options.page_align_shared_libraries,
   ]
 
   build_utils.CallAndWriteDepfileIfStale(
@@ -129,57 +89,34 @@
       output_paths=[options.final_apk_path])
 
 
+def _NormalizeZip(path):
+  with tempfile.NamedTemporaryFile(suffix='.zip') as hermetic_signed_apk:
+    with zipfile.ZipFile(path, 'r') as zi:
+      with zipfile.ZipFile(hermetic_signed_apk, 'w') as zo:
+        for info in zi.infolist():
+          # Ignore 'extended local file headers'. Python doesn't write them
+          # properly (see https://bugs.python.org/issue1742205) which causes
+          # zipalign to miscalculate alignment. Since we don't use them except
+          # for alignment anyway, we write a stripped file here and let
+          # zipalign add them properly later. eLFHs are controlled by 'general
+          # purpose bit flag 03' (0x08) so we mask that out.
+          info.flag_bits = info.flag_bits & 0xF7
+
+          info.date_time = build_utils.HERMETIC_TIMESTAMP
+          zo.writestr(info, zi.read(info.filename))
+
+    shutil.copy(hermetic_signed_apk.name, path)
+
+
 def FinalizeApk(options):
-  with tempfile.NamedTemporaryFile() as signed_apk_path_tmp, \
-      tempfile.NamedTemporaryFile() as apk_to_sign_tmp:
-
-    if options.load_library_from_zip:
-      # We alter the name of the library so that the Android Package Manager
-      # does not extract it into a separate file. This must be done before
-      # signing, as the filename is part of the signed manifest. At the same
-      # time we uncompress the library, which is necessary so that it can be
-      # loaded directly from the APK.
-      # Move the library to a page boundary by adding a page alignment file.
-      apk_to_sign = apk_to_sign_tmp.name
-      RenameInflateAndAddPageAlignment(
-          options.rezip_apk_jar_path, options.unsigned_apk_path, apk_to_sign)
-    else:
-      apk_to_sign = options.unsigned_apk_path
-
+  with tempfile.NamedTemporaryFile() as signed_apk_path_tmp:
     signed_apk_path = signed_apk_path_tmp.name
     JarSigner(options.key_path, options.key_name, options.key_passwd,
-              apk_to_sign, signed_apk_path)
+              options.unsigned_apk_path, signed_apk_path)
+    # Make the newly added signing files hermetic.
+    _NormalizeZip(signed_apk_path)
 
-    # Make the signing files hermetic.
-    with tempfile.NamedTemporaryFile(suffix='.zip') as hermetic_signed_apk:
-      with zipfile.ZipFile(signed_apk_path, 'r') as zi:
-        with zipfile.ZipFile(hermetic_signed_apk, 'w') as zo:
-          for info in zi.infolist():
-            # Ignore 'extended local file headers'. Python doesn't write them
-            # properly (see https://bugs.python.org/issue1742205) which causes
-            # zipalign to miscalculate alignment. Since we don't use them except
-            # for alignment anyway, we write a stripped file here and let
-            # zipalign add them properly later. eLFHs are controlled by 'general
-            # purpose bit flag 03' (0x08) so we mask that out.
-            info.flag_bits = info.flag_bits & 0xF7
-
-            info.date_time = build_utils.HERMETIC_TIMESTAMP
-            zo.writestr(info, zi.read(info.filename))
-
-      shutil.copy(hermetic_signed_apk.name, signed_apk_path)
-
-    if options.load_library_from_zip:
-      # Reorder the contents of the APK. This re-establishes the canonical
-      # order which means the library will be back at its page aligned location.
-      # This step also aligns uncompressed items to 4 bytes.
-      ReorderAndAlignApk(
-          options.rezip_apk_jar_path, signed_apk_path, options.final_apk_path)
-    else:
-      # Align uncompressed items to 4 bytes
-      AlignApk(options.zipalign_path,
-               options.page_align_shared_libraries,
-               signed_apk_path,
-               options.final_apk_path)
+    AlignApk(options.zipalign_path, signed_apk_path, options.final_apk_path)
 
 
 if __name__ == '__main__':
diff --git a/build/android/rezip/BUILD.gn b/build/android/rezip/BUILD.gn
deleted file mode 100644
index 1ace37c..0000000
--- a/build/android/rezip/BUILD.gn
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/android/rules.gni")
-
-java_library("rezip") {
-  jar_path = "$root_build_dir/lib.java/rezip_apk.jar"
-  java_files = [ "RezipApk.java" ]
-}
diff --git a/build/android/rezip/RezipApk.java b/build/android/rezip/RezipApk.java
deleted file mode 100644
index 43d75447..0000000
--- a/build/android/rezip/RezipApk.java
+++ /dev/null
@@ -1,448 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import java.util.regex.Pattern;
-import java.util.zip.CRC32;
-
-/**
- * Command line tool used to build APKs which support loading the native code library
- * directly from the APK file. To construct the APK we rename the native library by
- * adding the prefix "crazy." to the filename. This is done to prevent the Android
- * Package Manager from extracting the library. The native code must be page aligned
- * and uncompressed. The page alignment is implemented by adding a zero filled file
- * in front of the the native code library. This tool is designed so that running
- * SignApk and/or zipalign on the resulting APK does not break the page alignment.
- * This is achieved by outputing the filenames in the same canonical order used
- * by SignApk and adding the same alignment fields added by zipalign.
- */
-class RezipApk {
-    // Alignment to use for non-compressed files (must match zipalign).
-    private static final int ALIGNMENT = 4;
-
-    // Alignment to use for non-compressed *.so files
-    private static final int LIBRARY_ALIGNMENT = 4096;
-
-    // Files matching this pattern are not copied to the output when adding alignment.
-    // When reordering and verifying the APK they are copied to the end of the file.
-    private static Pattern sMetaFilePattern =
-            Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert))|("
-                    + Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
-
-    // Pattern for matching a shared library in the APK
-    private static Pattern sLibraryPattern = Pattern.compile("^lib/[^/]*/lib.*[.]so$");
-    // Pattern for match the crazy linker in the APK
-    private static Pattern sCrazyLinkerPattern =
-            Pattern.compile("^lib/[^/]*/libchromium_android_linker.so$");
-    // Pattern for matching a crazy loaded shared library in the APK
-    private static Pattern sCrazyLibraryPattern = Pattern.compile("^lib/[^/]*/crazy.lib.*[.]so$");
-
-    private static boolean isLibraryFilename(String filename) {
-        return sLibraryPattern.matcher(filename).matches()
-                && !sCrazyLinkerPattern.matcher(filename).matches();
-    }
-
-    private static boolean isCrazyLibraryFilename(String filename) {
-        return sCrazyLibraryPattern.matcher(filename).matches();
-    }
-
-    private static String renameLibraryForCrazyLinker(String filename) {
-        int lastSlash = filename.lastIndexOf('/');
-        // We rename the library, so that the Android Package Manager
-        // no longer extracts the library.
-        return filename.substring(0, lastSlash + 1) + "crazy." + filename.substring(lastSlash + 1);
-    }
-
-    /**
-     * Wraps another output stream, counting the number of bytes written.
-     */
-    private static class CountingOutputStream extends OutputStream {
-        private long mCount = 0;
-        private OutputStream mOut;
-
-        public CountingOutputStream(OutputStream out) {
-            this.mOut = out;
-        }
-
-        /** Returns the number of bytes written. */
-        public long getCount() {
-            return mCount;
-        }
-
-        @Override public void write(byte[] b, int off, int len) throws IOException {
-            mOut.write(b, off, len);
-            mCount += len;
-        }
-
-        @Override public void write(int b) throws IOException {
-            mOut.write(b);
-            mCount++;
-        }
-
-        @Override public void close() throws IOException {
-            mOut.close();
-        }
-
-        @Override public void flush() throws IOException {
-            mOut.flush();
-        }
-    }
-
-    private static String outputName(JarEntry entry, boolean rename) {
-        String inName = entry.getName();
-        if (rename && entry.getSize() > 0 && isLibraryFilename(inName)) {
-            return renameLibraryForCrazyLinker(inName);
-        }
-        return inName;
-    }
-
-    /**
-     * Comparator used to sort jar entries from the input file.
-     * Sorting is done based on the output filename (which maybe renamed).
-     * Filenames are in natural string order, except that filenames matching
-     * the meta-file pattern are always after other files. This is so the manifest
-     * and signature are at the end of the file after any alignment file.
-     */
-    private static class EntryComparator implements Comparator<JarEntry> {
-        private boolean mRename;
-
-        public EntryComparator(boolean rename) {
-            mRename = rename;
-        }
-
-        @Override
-        public int compare(JarEntry j1, JarEntry j2) {
-            String o1 = outputName(j1, mRename);
-            String o2 = outputName(j2, mRename);
-            boolean o1Matches = sMetaFilePattern.matcher(o1).matches();
-            boolean o2Matches = sMetaFilePattern.matcher(o2).matches();
-            if (o1Matches != o2Matches) {
-                return o1Matches ? 1 : -1;
-            } else {
-                return o1.compareTo(o2);
-            }
-        }
-    }
-
-    // Build an ordered list of jar entries. The jar entries from the input are
-    // sorted based on the output filenames (which maybe renamed). If |omitMetaFiles|
-    // is true do not include the jar entries for the META-INF files.
-    // Entries are ordered in the deterministic order used by SignApk.
-    private static List<JarEntry> getOutputFileOrderEntries(
-            JarFile jar, boolean omitMetaFiles, boolean rename) {
-        List<JarEntry> entries = new ArrayList<JarEntry>();
-        for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
-            JarEntry entry = e.nextElement();
-            if (entry.isDirectory()) {
-                continue;
-            }
-            if (omitMetaFiles && sMetaFilePattern.matcher(entry.getName()).matches()) {
-                continue;
-            }
-            entries.add(entry);
-        }
-
-        // We sort the input entries by name. When present META-INF files
-        // are sorted to the end.
-        Collections.sort(entries, new EntryComparator(rename));
-        return entries;
-    }
-
-    /**
-     * Add a zero filled alignment file at this point in the zip file,
-     * The added file will be added before |name| and after |prevName|.
-     * The size of the alignment file is such that the location of the
-     * file |name| will be on a LIBRARY_ALIGNMENT boundary.
-     *
-     * Note this arrangement is devised so that running SignApk and/or zipalign on the resulting
-     * file will not alter the alignment.
-     *
-     * @param offset number of bytes into the output file at this point.
-     * @param timestamp time in millis since the epoch to include in the header.
-     * @param name the name of the library filename.
-     * @param prevName the name of the previous file in the archive (or null).
-     * @param out jar output stream to write the alignment file to.
-     *
-     * @throws IOException if the output file can not be written.
-     */
-    private static void addAlignmentFile(
-            long offset, long timestamp, String name, String prevName,
-            JarOutputStream out) throws IOException {
-
-        // Compute the start and alignment of the library, as if it was next.
-        int headerSize = JarFile.LOCHDR + name.length();
-        long libOffset = offset + headerSize;
-        int libNeeded = LIBRARY_ALIGNMENT - (int) (libOffset % LIBRARY_ALIGNMENT);
-        if (libNeeded == LIBRARY_ALIGNMENT) {
-            // Already aligned, no need to added alignment file.
-            return;
-        }
-
-        // Check that there is not another file between the library and the
-        // alignment file.
-        String alignName = name.substring(0, name.length() - 2) + "align";
-        if (prevName != null && prevName.compareTo(alignName) >= 0) {
-            throw new UnsupportedOperationException(
-                "Unable to insert alignment file, because there is "
-                + "another file in front of the file to be aligned. "
-                + "Other file: " + prevName + " Alignment file: " + alignName
-                + " file: " + name);
-        }
-
-        // Compute the size of the alignment file header.
-        headerSize = JarFile.LOCHDR + alignName.length();
-        // We are going to add an alignment file of type STORED. This file
-        // will itself induce a zipalign alignment adjustment.
-        int extraNeeded =
-                (ALIGNMENT - (int) ((offset + headerSize) % ALIGNMENT)) % ALIGNMENT;
-        headerSize += extraNeeded;
-
-        if (libNeeded < headerSize + 1) {
-            // The header was bigger than the alignment that we need, add another page.
-            libNeeded += LIBRARY_ALIGNMENT;
-        }
-        // Compute the size of the alignment file.
-        libNeeded -= headerSize;
-
-        // Build the header for the alignment file.
-        byte[] zeroBuffer = new byte[libNeeded];
-        JarEntry alignEntry = new JarEntry(alignName);
-        alignEntry.setMethod(JarEntry.STORED);
-        alignEntry.setSize(libNeeded);
-        alignEntry.setTime(timestamp);
-        CRC32 crc = new CRC32();
-        crc.update(zeroBuffer);
-        alignEntry.setCrc(crc.getValue());
-
-        if (extraNeeded != 0) {
-            alignEntry.setExtra(new byte[extraNeeded]);
-        }
-
-        // Output the alignment file.
-        out.putNextEntry(alignEntry);
-        out.write(zeroBuffer);
-        out.closeEntry();
-        out.flush();
-    }
-
-    // Make a JarEntry for the output file which corresponds to the input
-    // file. The output file will be called |name|. The output file will always
-    // be uncompressed (STORED). If the input is not STORED it is necessary to inflate
-    // it to compute the CRC and size of the output entry.
-    private static JarEntry makeStoredEntry(String name, JarEntry inEntry, JarFile in)
-            throws IOException {
-        JarEntry outEntry = new JarEntry(name);
-        outEntry.setMethod(JarEntry.STORED);
-
-        if (inEntry.getMethod() == JarEntry.STORED) {
-            outEntry.setCrc(inEntry.getCrc());
-            outEntry.setSize(inEntry.getSize());
-        } else {
-            // We are inflating the file. We need to compute the CRC and size.
-            byte[] buffer = new byte[4096];
-            CRC32 crc = new CRC32();
-            int size = 0;
-            int num;
-            InputStream data = in.getInputStream(inEntry);
-            while ((num = data.read(buffer)) > 0) {
-                crc.update(buffer, 0, num);
-                size += num;
-            }
-            data.close();
-            outEntry.setCrc(crc.getValue());
-            outEntry.setSize(size);
-        }
-        return outEntry;
-    }
-
-    /**
-     * Copy the contents of the input APK file to the output APK file. If |rename| is
-     * true then non-empty libraries (*.so) in the input will be renamed by prefixing
-     * "crazy.". This is done to prevent the Android Package Manager extracting the
-     * library. Note the crazy linker itself is not renamed, for bootstrapping reasons.
-     * Empty libraries are not renamed (they are in the APK to workaround a bug where
-     * the Android Package Manager fails to delete old versions when upgrading).
-     * There must be exactly one "crazy" library in the output stream. The "crazy"
-     * library will be uncompressed and page aligned in the output stream. Page
-     * alignment is implemented by adding a zero filled file, regular alignment is
-     * implemented by adding a zero filled extra field to the zip file header. If
-     * |addAlignment| is true a page alignment file is added, otherwise the "crazy"
-     * library must already be page aligned. Care is taken so that the output is generated
-     * in the same way as SignApk. This is important so that running SignApk and
-     * zipalign on the output does not break the page alignment. The archive may not
-     * contain a "*.apk" as SignApk has special nested signing logic that we do not
-     * support.
-     *
-     * @param in The input APK File.
-     * @param out The output APK stream.
-     * @param countOut Counting output stream (to measure the current offset).
-     * @param addAlignment Whether to add the alignment file or just check.
-     * @param rename Whether to rename libraries to be "crazy".
-     *
-     * @throws IOException if the output file can not be written.
-     */
-    private static void rezip(
-            JarFile in, JarOutputStream out, CountingOutputStream countOut,
-            boolean addAlignment, boolean rename) throws IOException {
-
-        List<JarEntry> entries = getOutputFileOrderEntries(in, addAlignment, rename);
-        long timestamp = System.currentTimeMillis();
-        byte[] buffer = new byte[4096];
-        boolean firstEntry = true;
-        String prevName = null;
-        int numCrazy = 0;
-        for (JarEntry inEntry : entries) {
-            // Rename files, if specied.
-            String name = outputName(inEntry, rename);
-            if (name.endsWith(".apk")) {
-                throw new UnsupportedOperationException(
-                        "Nested APKs are not supported: " + name);
-            }
-
-            // Build the header.
-            JarEntry outEntry = null;
-            boolean isCrazy = isCrazyLibraryFilename(name);
-            if (isCrazy) {
-                // "crazy" libraries are alway output uncompressed (STORED).
-                outEntry = makeStoredEntry(name, inEntry, in);
-                numCrazy++;
-                if (numCrazy > 1) {
-                    throw new UnsupportedOperationException(
-                            "Found more than one library\n"
-                            + "Multiple libraries are not supported for APKs that use "
-                            + "'load_library_from_zip'.\n"
-                            + "See crbug/388223.\n"
-                            + "Note, check that your build is clean.\n"
-                            + "An unclean build can incorrectly incorporate old "
-                            + "libraries in the APK.");
-                }
-            } else if (inEntry.getMethod() == JarEntry.STORED) {
-                // Preserve the STORED method of the input entry.
-                outEntry = new JarEntry(inEntry);
-                outEntry.setExtra(null);
-            } else {
-                // Create a new entry so that the compressed len is recomputed.
-                outEntry = new JarEntry(name);
-            }
-            outEntry.setTime(timestamp);
-
-            // Compute and add alignment
-            long offset = countOut.getCount();
-            if (firstEntry) {
-                // The first entry in a jar file has an extra field of
-                // four bytes that you can't get rid of; any extra
-                // data you specify in the JarEntry is appended to
-                // these forced four bytes.  This is JAR_MAGIC in
-                // JarOutputStream; the bytes are 0xfeca0000.
-                firstEntry = false;
-                offset += 4;
-            }
-            if (outEntry.getMethod() == JarEntry.STORED) {
-                if (isCrazy) {
-                    if (addAlignment) {
-                        addAlignmentFile(offset, timestamp, name, prevName, out);
-                    }
-                    // We check that we did indeed get to a page boundary.
-                    offset = countOut.getCount() + JarFile.LOCHDR + name.length();
-                    if ((offset % LIBRARY_ALIGNMENT) != 0) {
-                        throw new AssertionError(
-                                "Library was not page aligned when verifying page alignment. "
-                                + "Library name: " + name + " Expected alignment: "
-                                + LIBRARY_ALIGNMENT + "Offset: " + offset + " Error: "
-                                + (offset % LIBRARY_ALIGNMENT));
-                    }
-                } else {
-                    // This is equivalent to zipalign.
-                    offset += JarFile.LOCHDR + name.length();
-                    int needed = (ALIGNMENT - (int) (offset % ALIGNMENT)) % ALIGNMENT;
-                    if (needed != 0) {
-                        outEntry.setExtra(new byte[needed]);
-                    }
-                }
-            }
-            out.putNextEntry(outEntry);
-
-            // Copy the data from the input to the output
-            int num;
-            InputStream data = in.getInputStream(inEntry);
-            while ((num = data.read(buffer)) > 0) {
-                out.write(buffer, 0, num);
-            }
-            data.close();
-            out.closeEntry();
-            out.flush();
-            prevName = name;
-        }
-        if (numCrazy == 0) {
-            throw new AssertionError("There was no crazy library in the archive");
-        }
-    }
-
-    private static void usage() {
-        System.err.println("Usage: prealignapk (addalignment|reorder) input.apk output.apk");
-        System.err.println("\"crazy\" libraries are always inflated in the output");
-        System.err.println(
-                "  renamealign  - rename libraries with \"crazy.\" prefix and add alignment file");
-        System.err.println("  align        - add alignment file");
-        System.err.println("  reorder      - re-creates canonical ordering and checks alignment");
-        System.exit(2);
-    }
-
-    public static void main(String[] args) throws IOException {
-        if (args.length != 3) usage();
-
-        boolean addAlignment = false;
-        boolean rename = false;
-        if (args[0].equals("renamealign")) {
-            // Normal case. Before signing we rename the library and add an alignment file.
-            addAlignment = true;
-            rename = true;
-        } else if (args[0].equals("align")) {
-            // LGPL compliance case. Before signing, we add an alignment file to a
-            // reconstructed APK which already contains the "crazy" library.
-            addAlignment = true;
-            rename = false;
-        } else if (args[0].equals("reorder")) {
-            // Normal case. After jarsigning we write the file in the canonical order and check.
-            addAlignment = false;
-        } else {
-            usage();
-        }
-
-        String inputFilename = args[1];
-        String outputFilename = args[2];
-
-        JarFile inputJar = null;
-        FileOutputStream outputFile = null;
-
-        try {
-            inputJar = new JarFile(new File(inputFilename), true);
-            outputFile = new FileOutputStream(outputFilename);
-
-            CountingOutputStream outCount = new CountingOutputStream(outputFile);
-            JarOutputStream outputJar = new JarOutputStream(outCount);
-
-            // Match the compression level used by SignApk.
-            outputJar.setLevel(9);
-
-            rezip(inputJar, outputJar, outCount, addAlignment, rename);
-            outputJar.close();
-        } finally {
-            if (inputJar != null) inputJar.close();
-            if (outputFile != null) outputFile.close();
-        }
-    }
-}
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index f6052ae..6883600 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -32,7 +32,6 @@
 
   # TODO(agrieve): Rename targets below to match above patterns.
   "*android_webview/glue:glue",
-  "//build/android/rezip:rezip",
   "//chrome/test/android/cast_emulator:cast_emulator",
 ]
 
@@ -1385,7 +1384,6 @@
   #   keystore_path: Path to keystore to use for signing.
   #   keystore_name: Key alias to use.
   #   keystore_password: Keystore password.
-  #   rezip_apk: Whether to add crazy-linker alignment.
   template("finalize_apk") {
     action(target_name) {
       deps = []
@@ -1428,20 +1426,6 @@
         "--key-passwd",
         invoker.keystore_password,
       ]
-      if (defined(invoker.rezip_apk) && invoker.rezip_apk) {
-        deps += [ "//build/android/rezip" ]
-        _rezip_jar_path = "$root_build_dir/lib.java/rezip_apk.jar"
-        args += [
-          "--load-library-from-zip=1",
-          "--rezip-apk-jar-path",
-          rebase_path(_rezip_jar_path, root_build_dir),
-        ]
-      }
-
-      if (defined(invoker.page_align_shared_libraries) &&
-          invoker.page_align_shared_libraries) {
-        args += [ "--page-align-shared-libraries" ]
-      }
     }
   }
 
@@ -1684,6 +1668,9 @@
                                "uncompress_shared_libraries",
                                "write_asset_list",
                              ])
+      if (!defined(uncompress_shared_libraries)) {
+        uncompress_shared_libraries = _load_library_from_apk
+      }
       deps = _deps + [ ":${_package_resources_target_name}" ]
       native_libs = _native_libs + _native_libs_even_when_incremental
 
@@ -1704,6 +1691,9 @@
                                "secondary_native_libs",
                                "uncompress_shared_libraries",
                              ])
+      if (!defined(uncompress_shared_libraries)) {
+        uncompress_shared_libraries = _load_library_from_apk
+      }
       _dex_target = "//build/android/incremental_install:bootstrap_java__dex"
       deps = _incremental_deps + [
                ":${_incremental_package_resources_target_name}",
@@ -1730,14 +1720,11 @@
 
     _finalize_apk_rule_name = "${target_name}__finalize"
     finalize_apk(_finalize_apk_rule_name) {
-      forward_variables_from(invoker, [ "page_align_shared_libraries" ])
-
       input_apk_path = _packaged_apk_path
       output_apk_path = _final_apk_path
       keystore_path = _keystore_path
       keystore_name = _keystore_name
       keystore_password = _keystore_password
-      rezip_apk = _load_library_from_apk
 
       public_deps = [
         # Generator of the _packaged_apk_path this target takes as input.
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 2a45db4..6d286fe 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2007,15 +2007,7 @@
     _extra_native_libs_deps = []
     assert(_extra_native_libs_deps == [])  # Mark as used.
     _extra_native_libs_even_when_incremental = []
-    _extra_native_libs_even_when_incremental_deps = []
-    assert(_extra_native_libs_even_when_incremental_deps == [])  # Mark as used.
     if (_native_libs_deps != []) {
-      # zipalign can't align gdb_server, don't pack gdbserver temporarily.
-      if (is_debug && (!defined(invoker.page_align_shared_libraries) ||
-                       !invoker.page_align_shared_libraries)) {
-        _extra_native_libs_even_when_incremental = [ android_gdbserver ]
-      }
-
       if (_use_chromium_linker) {
         _extra_native_libs =
             [ "$root_shlib_dir/libchromium_android_linker$shlib_extension" ]
@@ -2037,7 +2029,6 @@
                                "deps",
                                "extensions_to_not_compress",
                                "language_splits",
-                               "page_align_shared_libraries",
                                "public_deps",
                                "secondary_native_libs",
                                "shared_resources",
@@ -2087,7 +2078,6 @@
            _extra_native_libs_even_when_incremental != []) &&
           !_create_abi_split) {
         deps += _native_libs_deps + _extra_native_libs_deps +
-                _extra_native_libs_even_when_incremental_deps +
                 [ _native_libs_file_arg_dep ]
         native_libs_filearg = _native_libs_file_arg
         native_libs = _extra_native_libs
@@ -2148,9 +2138,7 @@
                                  "public_deps",
                                ])
 
-        incremental_deps =
-            deps + _extra_native_libs_even_when_incremental_deps +
-            [ ":$_manifest_rule" ]
+        incremental_deps = deps + [ ":$_manifest_rule" ]
         deps = []
         deps = incremental_deps + _native_libs_deps + _extra_native_libs_deps +
                [ _native_libs_file_arg_dep ]
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index f3a6962..b5231b153 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1560,6 +1560,16 @@
     } else {
       ldflags = [ "/DEBUG" ]
     }
+
+    if (is_clang) {
+      # /DEBUG:FASTLINK requires every object file to have standalone debug
+      # information.
+      if (is_win_fastlink) {
+        cflags += [ "-fstandalone-debug" ]
+      } else {
+        cflags += [ "-fno-standalone-debug" ]
+      }
+    }
   } else {
     if (is_mac || is_ios) {
       cflags = [ "-gdwarf-2" ]
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index 6646b59..09b345ff 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -104,7 +104,6 @@
 
     # Configrations to make android load shared library from APK.
     uncompress_shared_libraries = true
-    page_align_shared_libraries = true
 
     forward_variables_from(invoker, "*")
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index fcb1508..3f00b3a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -1742,14 +1742,6 @@
     }
 
     /**
-     * Tests if VR Shell (the mode displaying browser UI and tab contents in VR) is currently
-     * enabled.
-     */
-    public boolean isVrShellEnabled() {
-        return false;
-    }
-
-    /**
      * Add a view to the set of views that obscure the content of all tabs for
      * accessibility. As long as this set is nonempty, all tabs should be
      * hidden from the accessibility tree.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index d44997c..243db83 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -1652,14 +1652,6 @@
         setMergedInstanceTaskId(getTaskId());
     }
 
-    /**
-     * See VrShellDelegate#isVrShellEnabled()
-     */
-    @Override
-    public boolean isVrShellEnabled() {
-        return mVrShellDelegate.isVrShellEnabled();
-    }
-
     // TODO(mthiesse): Toggle toolbar overlay, popups, etc.
     public void setUIVisibilityForVR(int visibility) {
         mControlContainer.setVisibility(visibility);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
index a3cbb6d..40364ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
@@ -69,17 +69,19 @@
      * Reschedules the fetching of snippets.
      */
     public static void rescheduleFetching() {
-        nativeRescheduleFetching();
+        nativeRemoteSuggestionsSchedulerRescheduleFetching();
     }
 
+    /**
+     * Fetches remote suggestions in background.
+     */
     public static void fetchRemoteSuggestionsFromBackground() {
-        // Do not force regular background fetches.
-        nativeFetchRemoteSuggestionsInTheBackground();
+        nativeRemoteSuggestionsSchedulerOnFetchDue();
     }
 
     @Override
     public void fetchRemoteSuggestions() {
-        nativeFetchRemoteSuggestions();
+        nativeReloadSuggestions(mNativeSnippetsBridge);
     }
 
     @Override
@@ -271,9 +273,9 @@
 
     private native long nativeInit(Profile profile);
     private native void nativeDestroy(long nativeNTPSnippetsBridge);
-    private static native void nativeFetchRemoteSuggestions();
-    private static native void nativeFetchRemoteSuggestionsInTheBackground();
-    private static native void nativeRescheduleFetching();
+    private native void nativeReloadSuggestions(long nativeNTPSnippetsBridge);
+    private static native void nativeRemoteSuggestionsSchedulerOnFetchDue();
+    private static native void nativeRemoteSuggestionsSchedulerRescheduleFetching();
     private native int[] nativeGetCategories(long nativeNTPSnippetsBridge);
     private native int nativeGetCategoryStatus(long nativeNTPSnippetsBridge, int category);
     private native SuggestionsCategoryInfo nativeGetCategoryInfo(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
index c2bfd8f9..03aa22c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
@@ -51,6 +51,13 @@
     @IntDef({ENTER_VR_NOT_NECESSARY, ENTER_VR_CANCELLED, ENTER_VR_REQUESTED, ENTER_VR_SUCCEEDED })
     public @interface EnterVRResult {}
 
+    public static final int VR_NOT_AVAILABLE = 0;
+    public static final int VR_CARDBOARD = 1;
+    public static final int VR_DAYDREAM = 2; // Supports both Cardboard and Daydream viewer.
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({VR_NOT_AVAILABLE, VR_CARDBOARD, VR_DAYDREAM})
+    public @interface VrSupportLevel {}
+
     // TODO(bshe): These should be replaced by string provided by NDK. Currently, it only available
     // in SDK and we don't want add dependency to SDK just to get these strings.
     private static final String DAYDREAM_VR_EXTRA = "android.intent.extra.VR_LAUNCH";
@@ -63,14 +70,13 @@
     private static final long REENTER_VR_TIMEOUT_MS = 1000;
 
     private final ChromeTabbedActivity mActivity;
-    private final TabObserver mTabObserver;
-    private final Intent mEnterVRIntent;
+    private TabObserver mTabObserver;
+    private Intent mEnterVRIntent;
 
-    private boolean mVrAvailable;
-    private boolean mCardboardSupportOnly = true;
-    private Boolean mVrShellEnabled;
+    @VrSupportLevel
+    private int mVrSupportLevel;
 
-    private VrClassesBuilder mVrClassesBuilder;
+    private final VrClassesBuilder mVrClassesBuilder;
     private VrShell mVrShell;
     private NonPresentingGvrContext mNonPresentingGvrContext;
     private VrDaydreamApi mVrDaydreamApi;
@@ -88,41 +94,52 @@
 
     public VrShellDelegate(ChromeTabbedActivity activity) {
         mActivity = activity;
+        mVrClassesBuilder = createVrClassesBuilder();
+        updateVrSupportLevel();
+    }
 
-        mVrAvailable = createVrClassesBuilder() && isVrCoreCompatible();
-        // Daydream ready devices support both Cardboard and Daydream mode.
-        if (mVrAvailable && createVrDaydreamApi() && mVrDaydreamApi.isDaydreamReadyDevice()) {
-            mCardboardSupportOnly = false;
+    /**
+     * Updates mVrSupportLevel to the correct value. isVrCoreCompatible might return different value
+     * at runtime.
+     */
+    // TODO(bshe): Find a place to call this function again, i.e. page refresh or onResume.
+    private void updateVrSupportLevel() {
+        if (mVrClassesBuilder == null || !isVrCoreCompatible()) {
+            mVrSupportLevel = VR_NOT_AVAILABLE;
+            mEnterVRIntent = null;
+            mTabObserver = null;
+            return;
         }
 
-        if (mVrAvailable && !mCardboardSupportOnly) {
+        if (mVrDaydreamApi == null) {
+            mVrDaydreamApi = mVrClassesBuilder.createVrDaydreamApi();
+        }
+
+        if (mEnterVRIntent == null) {
             mEnterVRIntent =
                     mVrDaydreamApi.createVrIntent(new ComponentName(mActivity, VR_ACTIVITY_ALIAS));
-        } else {
-            mEnterVRIntent = null;
         }
-        if (mVrAvailable) {
+
+        if (mTabObserver == null) {
             mTabObserver = new EmptyTabObserver() {
                 @Override
                 public void onContentChanged(Tab tab) {
                     if (tab.getNativePage() != null || tab.isShowingSadTab()) {
                         // For now we don't handle native pages. crbug.com/661609.
-                        shutdownVR(false, !mCardboardSupportOnly /* showTransition */);
+                        shutdownVR(false, mVrSupportLevel == VR_DAYDREAM /* showTransition */);
                     }
                 }
 
                 @Override
                 public void onWebContentsSwapped(
                         Tab tab, boolean didStartLoad, boolean didFinishLoad) {
-                    // TODO(mthiesse): Update the native WebContents pointer and compositor. This
-                    // doesn't seem to get triggered in VR Shell currently, but that's likely to
-                    // change
-                    // when we have omnibar navigation.
+                    // TODO(mthiesse): Update the native WebContents pointer and compositor.
+                    shutdownVR(false, mVrSupportLevel == VR_DAYDREAM /* showTransition */);
                 }
             };
-        } else {
-            mTabObserver = null;
         }
+
+        mVrSupportLevel = mVrDaydreamApi.isDaydreamReadyDevice() ? VR_DAYDREAM : VR_CARDBOARD;
     }
 
     /**
@@ -130,27 +147,25 @@
      * class can be initialized.
      */
     public void onNativeLibraryReady() {
-        if (!mVrAvailable) return;
+        if (mVrSupportLevel == VR_NOT_AVAILABLE) return;
         mNativeVrShellDelegate = nativeInit();
     }
 
     @SuppressWarnings("unchecked")
-    private boolean createVrClassesBuilder() {
+    private VrClassesBuilder createVrClassesBuilder() {
         try {
             Class<? extends VrClassesBuilder> vrClassesBuilderClass =
                     (Class<? extends VrClassesBuilder>) Class.forName(
                             "org.chromium.chrome.browser.vr_shell.VrClassesBuilderImpl");
             Constructor<?> vrClassesBuilderConstructor =
                     vrClassesBuilderClass.getConstructor(Activity.class);
-            mVrClassesBuilder =
-                    (VrClassesBuilder) vrClassesBuilderConstructor.newInstance(mActivity);
-            return true;
+            return (VrClassesBuilder) vrClassesBuilderConstructor.newInstance(mActivity);
         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                 | IllegalArgumentException | InvocationTargetException | NoSuchMethodException e) {
             if (!(e instanceof ClassNotFoundException)) {
                 Log.e(TAG, "Unable to instantiate VrClassesBuilder", e);
             }
-            return false;
+            return null;
         }
     }
 
@@ -159,7 +174,7 @@
      */
     public void enterVRFromIntent(Intent intent) {
         // Vr Intent is only used on Daydream devices.
-        if (!mVrAvailable || mCardboardSupportOnly) return;
+        if (mVrSupportLevel != VR_DAYDREAM) return;
         assert isVrIntent(intent);
         if (mListeningForWebVrActivateBeforePause && !mRequestedWebVR) {
             nativeDisplayActivate(mNativeVrShellDelegate);
@@ -223,7 +238,7 @@
         }
         // If vr isn't in the build, or we haven't initialized yet, or vr shell is not enabled and
         // this is not a web vr request, then return immediately.
-        if (!mVrAvailable || mNativeVrShellDelegate == 0
+        if (mVrSupportLevel == VR_NOT_AVAILABLE || mNativeVrShellDelegate == 0
                 || (!isVrShellEnabled() && !(mRequestedWebVR || mListeningForWebVrActivate))) {
             return false;
         }
@@ -275,11 +290,11 @@
      */
     @EnterVRResult
     public int enterVRIfNecessary() {
-        if (!mVrAvailable) return ENTER_VR_CANCELLED;
+        if (mVrSupportLevel == VR_NOT_AVAILABLE) return ENTER_VR_CANCELLED;
         if (mInVr) return ENTER_VR_NOT_NECESSARY;
         if (!canEnterVR(mActivity.getActivityTab())) return ENTER_VR_CANCELLED;
 
-        if (mCardboardSupportOnly || !mVrDaydreamApi.isDaydreamCurrentViewer()) {
+        if (mVrSupportLevel == VR_CARDBOARD || !mVrDaydreamApi.isDaydreamCurrentViewer()) {
             // Avoid using launchInVr which would trigger DON flow regardless current viewer type
             // due to the lack of support for unexported activities.
             return enterVR() ? ENTER_VR_SUCCEEDED : ENTER_VR_CANCELLED;
@@ -293,7 +308,7 @@
     private boolean exitWebVR() {
         if (!mInVr) return false;
         mVrShell.setWebVrModeEnabled(false);
-        if (mCardboardSupportOnly) {
+        if (mVrSupportLevel == VR_CARDBOARD) {
             // Transition screen is not available for Cardboard only (non-Daydream) devices.
             // TODO(bshe): Fix this once b/33490788 is fixed.
             shutdownVR(false, false);
@@ -315,9 +330,9 @@
      * Resumes VR Shell.
      */
     public void maybeResumeVR() {
-        if (!mVrAvailable) return;
-        if ((isVrShellEnabled() || mListeningForWebVrActivateBeforePause)
-                && !mCardboardSupportOnly) {
+        if (mVrSupportLevel == VR_NOT_AVAILABLE) return;
+        if (mVrSupportLevel == VR_DAYDREAM
+                && (isVrShellEnabled() || mListeningForWebVrActivateBeforePause)) {
             registerDaydreamIntent();
         }
         // If this is still set, it means the user backed out of the DON flow, and we won't be
@@ -351,7 +366,7 @@
             } finally {
                 StrictMode.setThreadPolicy(oldPolicy);
             }
-        } else if (!mCardboardSupportOnly && mVrDaydreamApi.isDaydreamCurrentViewer()
+        } else if (mVrSupportLevel == VR_DAYDREAM && mVrDaydreamApi.isDaydreamCurrentViewer()
                 && mLastVRExit + REENTER_VR_TIMEOUT_MS > SystemClock.uptimeMillis()) {
             enterVRIfNecessary();
         }
@@ -361,9 +376,9 @@
      * Pauses VR Shell.
      */
     public void maybePauseVR() {
-        if (!mVrAvailable) return;
+        if (mVrSupportLevel == VR_NOT_AVAILABLE) return;
 
-        if (!mCardboardSupportOnly) {
+        if (mVrSupportLevel == VR_DAYDREAM) {
             unregisterDaydreamIntent();
 
             // When the active web page has a vrdisplayactivate event handler,
@@ -390,14 +405,14 @@
      * @return Whether or not we exited VR.
      */
     public boolean exitVRIfNecessary(boolean isPausing) {
-        if (!mVrAvailable) return false;
+        if (mVrSupportLevel == VR_NOT_AVAILABLE) return false;
         if (!mInVr) return false;
         shutdownVR(isPausing, false);
         return true;
     }
 
     public void onExitVRResult(int resultCode) {
-        assert mVrAvailable;
+        assert mVrSupportLevel != VR_NOT_AVAILABLE;
         if (resultCode == Activity.RESULT_OK) {
             mVrShell.setVrModeEnabled(false);
         } else {
@@ -444,7 +459,7 @@
     private void setListeningForWebVrActivate(boolean listening) {
         // Non-Daydream devices may not have the concept of display activate. So disable
         // mListeningForWebVrActivate for them.
-        if (!mVrAvailable || mCardboardSupportOnly) return;
+        if (mVrSupportLevel != VR_DAYDREAM) return;
         mListeningForWebVrActivate = listening;
         if (listening) {
             registerDaydreamIntent();
@@ -479,21 +494,13 @@
     }
 
     private boolean isVrCoreCompatible() {
-        if (mVrClassesBuilder == null) return false;
-
-        if (mVrCoreVersionChecker != null) {
-            return mVrCoreVersionChecker.isVrCoreCompatible();
+        assert mVrClassesBuilder != null;
+        if (mVrCoreVersionChecker == null) {
+            mVrCoreVersionChecker = mVrClassesBuilder.createVrCoreVersionChecker();
         }
-        mVrCoreVersionChecker = mVrClassesBuilder.createVrCoreVersionChecker();
         return mVrCoreVersionChecker.isVrCoreCompatible();
     }
 
-    private boolean createVrDaydreamApi() {
-        if (mVrClassesBuilder == null) return false;
-        mVrDaydreamApi = mVrClassesBuilder.createVrDaydreamApi();
-        return true;
-    }
-
     private boolean createVrShell() {
         if (mVrClassesBuilder == null) return false;
         mVrShell = mVrClassesBuilder.createVrShell();
@@ -569,17 +576,13 @@
     /**
      * @return Whether or not VR Shell is currently enabled.
      */
-    public boolean isVrShellEnabled() {
-        // Disable VR Shell for Cardboard only (non-Daydream) devices.
-        // TODO(amp|bshe): Investigate if removing LibraryLoader.isInitialized from the check is
-        // possible.
-        if (!mVrAvailable || mCardboardSupportOnly || !LibraryLoader.isInitialized()) {
+    private boolean isVrShellEnabled() {
+        // Only enable ChromeVR (VrShell) on Daydream devices as it currently needs a Daydream
+        // controller.
+        if (mVrSupportLevel != VR_DAYDREAM) {
             return false;
         }
-        if (mVrShellEnabled == null) {
-            mVrShellEnabled = ChromeFeatureList.isEnabled(ChromeFeatureList.VR_SHELL);
-        }
-        return mVrShellEnabled;
+        return ChromeFeatureList.isEnabled(ChromeFeatureList.VR_SHELL);
     }
 
     /**
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index 1460145..1b083650 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -355,8 +355,12 @@
 
 service_manifest_overlay("chrome_content_browser_manifest_overlay") {
   source = "//chrome/browser/chrome_content_browser_manifest_overlay.json"
-  packaged_services = [ "image_decoder" ]
+  packaged_services = [
+    "image_decoder",
+    "preferences",
+  ]
   deps = [
+    "//chrome/browser:preferences_manifest",
     "//services/image_decoder:manifest",
   ]
 }
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 9dd821c..4e3e5b1 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -6370,7 +6370,7 @@
     Manage your Android <ph name="BEGIN_LINK">&lt;a is="action-link" id="android-apps-settings-link" role="link"&gt;</ph>preferences<ph name="END_LINK">&lt;/a&gt;</ph>.
   </message>
   <message name="IDS_ARC_NOTIFICATION_TITLE" desc="Title of the first-run notification that enables Android apps.">
-    Google Play Store
+    Google Play Store (beta)
   </message>
   <message name="IDS_ARC_NOTIFICATION_MESSAGE" desc="Description message of the first-run notification that enables Android apps.">
     Over a million apps and games now available on your <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph>.
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 32d7c62..7ccdd4b 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -17,6 +17,7 @@
 import("//printing/features/features.gni")
 import("//rlz/features/features.gni")
 import("//sandbox/features.gni")
+import("//services/service_manager/public/service_manifest.gni")
 import("//third_party/protobuf/proto_library.gni")
 import("//ui/base/ui_features.gni")
 
@@ -925,6 +926,10 @@
     "prefs/pref_metrics_service.h",
     "prefs/pref_service_syncable_util.cc",
     "prefs/pref_service_syncable_util.h",
+    "prefs/preferences_connection_manager.cc",
+    "prefs/preferences_connection_manager.h",
+    "prefs/preferences_manager.cc",
+    "prefs/preferences_manager.h",
     "prefs/profile_pref_store_manager.cc",
     "prefs/profile_pref_store_manager.h",
     "prefs/session_startup_pref.cc",
@@ -1524,6 +1529,7 @@
     "//printing/features",
     "//rlz/features",
     "//services/image_decoder/public/cpp",
+    "//services/preferences/public/interfaces/",
     "//services/service_manager/public/cpp",
     "//skia",
     "//sql",
@@ -1564,7 +1570,9 @@
     "//ui/web_dialogs",
     "//v8",
   ]
-  data_deps = []
+  data_deps = [
+    ":preferences_manifest",
+  ]
 
   if (is_chromeos && use_cras) {
     defines += [ "USE_CRAS" ]
@@ -4474,3 +4482,8 @@
     ]
   }
 }
+
+service_manifest("preferences_manifest") {
+  name = "preferences"
+  source = "prefs/preferences_manifest.json"
+}
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index f105606..663fa6f 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -48,6 +48,7 @@
   "+rlz",
   "+sandbox/win/src",  # The path doesn't say it, but this is the Windows sandbox.
   "+services/image_decoder/public/interfaces",
+  "+services/preferences/public/interfaces",
   "+services/service_manager",
   "+skia/ext",
   "+syzygy/kasko",
diff --git a/chrome/browser/android/ntp/ntp_snippets_bridge.cc b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
index e0579c7..771a42b 100644
--- a/chrome/browser/android/ntp/ntp_snippets_bridge.cc
+++ b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
@@ -24,6 +24,7 @@
 #include "components/ntp_snippets/content_suggestions_metrics.h"
 #include "components/ntp_snippets/pref_names.h"
 #include "components/ntp_snippets/remote/remote_suggestions_provider.h"
+#include "components/ntp_snippets/remote/remote_suggestions_scheduler.h"
 #include "components/prefs/pref_service.h"
 #include "jni/SnippetsBridge_jni.h"
 #include "ui/base/window_open_disposition.h"
@@ -95,7 +96,7 @@
   return result;
 }
 
-ntp_snippets::RemoteSuggestionsProvider* GetRemoteSuggestionsProvider() {
+ntp_snippets::RemoteSuggestionsScheduler* GetRemoteSuggestionsScheduler() {
   ntp_snippets::ContentSuggestionsService* content_suggestions_service =
       ContentSuggestionsServiceFactory::GetForProfile(
           ProfileManager::GetLastUsedProfile());
@@ -103,7 +104,7 @@
   if (!content_suggestions_service) {
     return nullptr;
   }
-  return content_suggestions_service->ntp_snippets_service();
+  return content_suggestions_service->remote_suggestions_scheduler();
 }
 
 }  // namespace
@@ -115,57 +116,33 @@
   return reinterpret_cast<intptr_t>(snippets_bridge);
 }
 
-static void FetchRemoteSuggestions(JNIEnv* env,
-                                   const JavaParamRef<jclass>& caller) {
-  ntp_snippets::RemoteSuggestionsProvider* remote_suggestions_provider =
-      GetRemoteSuggestionsProvider();
-  // Can be null if the feature has been disabled but the scheduler has not been
-  // unregistered yet. The next start should unregister it.
-  if (!remote_suggestions_provider) {
-    return;
-  }
-  remote_suggestions_provider->FetchSnippetsForAllCategories();
-}
-
-static void FetchRemoteSuggestionsInTheBackground(
+// Initiates a background fetch for remote suggestions.
+static void RemoteSuggestionsSchedulerOnFetchDue(
     JNIEnv* env,
     const JavaParamRef<jclass>& caller) {
-  ntp_snippets::RemoteSuggestionsProvider* remote_suggestions_provider =
-      GetRemoteSuggestionsProvider();
-  // Can be null if the feature has been disabled but the scheduler has not been
-  // unregistered yet. The next start should unregister it.
-  if (!remote_suggestions_provider) {
+  ntp_snippets::RemoteSuggestionsScheduler* scheduler =
+      GetRemoteSuggestionsScheduler();
+  if (!scheduler) {
     return;
   }
-  remote_suggestions_provider->FetchSnippetsInTheBackground();
+
+  scheduler->OnPersistentSchedulerWakeUp();
 }
 
 // Reschedules the fetching of snippets. If tasks are already scheduled, they
 // will be rescheduled anyway, so all running intervals will be reset.
-static void RescheduleFetching(JNIEnv* env,
-                               const JavaParamRef<jclass>& caller) {
-  Profile* profile = ProfileManager::GetLastUsedProfile();
-  // Temporary check while investigating crbug.com/647920.
-  CHECK(profile);
-
-  ntp_snippets::ContentSuggestionsService* content_suggestions_service =
-      ContentSuggestionsServiceFactory::GetForProfile(profile);
-
-  // Can maybe be null in some cases? (Incognito profile?) crbug.com/647920
-  if (!content_suggestions_service) {
-    return;
-  }
-
-  ntp_snippets::RemoteSuggestionsProvider* service =
-      content_suggestions_service->ntp_snippets_service();
-
+static void RemoteSuggestionsSchedulerRescheduleFetching(
+    JNIEnv* env,
+    const JavaParamRef<jclass>& caller) {
+  ntp_snippets::RemoteSuggestionsScheduler* scheduler =
+      GetRemoteSuggestionsScheduler();
   // Can be null if the feature has been disabled but the scheduler has not been
   // unregistered yet. The next start should unregister it.
-  if (!service) {
+  if (!scheduler) {
     return;
   }
 
-  service->RescheduleFetching(/*force=*/true);
+  scheduler->RescheduleFetching();
 }
 
 static void OnSuggestionTargetVisited(JNIEnv* env,
@@ -275,6 +252,12 @@
                  weak_ptr_factory_.GetWeakPtr(), category));
 }
 
+void NTPSnippetsBridge::ReloadSuggestions(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj) {
+  content_suggestions_service_->ReloadSuggestions();
+}
+
 void NTPSnippetsBridge::DismissSuggestion(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/android/ntp/ntp_snippets_bridge.h b/chrome/browser/android/ntp/ntp_snippets_bridge.h
index cf25199b..5baf93b 100644
--- a/chrome/browser/android/ntp/ntp_snippets_bridge.h
+++ b/chrome/browser/android/ntp/ntp_snippets_bridge.h
@@ -69,6 +69,10 @@
       jint j_category_id,
       const base::android::JavaParamRef<jobjectArray>& j_displayed_suggestions);
 
+  void ReloadSuggestions(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
+
   void DismissSuggestion(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/android/ntp/ntp_snippets_launcher.h b/chrome/browser/android/ntp/ntp_snippets_launcher.h
index 2dfbb4b..bd163a08 100644
--- a/chrome/browser/android/ntp/ntp_snippets_launcher.h
+++ b/chrome/browser/android/ntp/ntp_snippets_launcher.h
@@ -9,12 +9,13 @@
 #include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/time/time.h"
-#include "components/ntp_snippets/remote/ntp_snippets_scheduler.h"
+#include "components/ntp_snippets/remote/persistent_scheduler.h"
 
 // Android implementation of ntp_snippets::NTPSnippetsScheduler.
 // The NTPSnippetsLauncher singleton owns the Java SnippetsLauncher object, and
 // is used to schedule the fetching of snippets. Runs on the UI thread.
-class NTPSnippetsLauncher : public ntp_snippets::NTPSnippetsScheduler {
+class NTPSnippetsLauncher
+    : public ntp_snippets::PersistentScheduler {
  public:
   static NTPSnippetsLauncher* Get();
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index e26d7b0..0acf00e 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -61,6 +61,7 @@
 #include "chrome/browser/payments/payment_request_impl.h"
 #include "chrome/browser/permissions/permission_context_base.h"
 #include "chrome/browser/platform_util.h"
+#include "chrome/browser/prefs/preferences_connection_manager.h"
 #include "chrome/browser/prerender/prerender_final_status.h"
 #include "chrome/browser/prerender/prerender_manager.h"
 #include "chrome/browser/prerender/prerender_manager_factory.h"
@@ -195,6 +196,7 @@
 #include "ppapi/host/ppapi_host.h"
 #include "printing/features/features.h"
 #include "services/image_decoder/public/interfaces/constants.mojom.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "services/service_manager/public/cpp/interface_registry.h"
 #include "services/service_manager/public/cpp/service.h"
@@ -3085,6 +3087,14 @@
 #if defined(OS_CHROMEOS)
   content::ServiceManagerConnection::GetForProcess()->AddConnectionFilter(
       base::MakeUnique<chromeos::ChromeInterfaceFactory>());
+  {
+    content::ServiceInfo info;
+    info.factory = base::Bind([] {
+      return std::unique_ptr<service_manager::Service>(
+          base::MakeUnique<PreferencesConnectionManager>());
+    });
+    services->insert(std::make_pair(prefs::mojom::kServiceName, info));
+  }
 #endif  // OS_CHROMEOS
 }
 
diff --git a/chrome/browser/chrome_content_browser_manifest_overlay.json b/chrome/browser/chrome_content_browser_manifest_overlay.json
index 9cc37ffa..5d5b1e9 100644
--- a/chrome/browser/chrome_content_browser_manifest_overlay.json
+++ b/chrome/browser/chrome_content_browser_manifest_overlay.json
@@ -46,6 +46,7 @@
         "ash": [ "ash" ],
         "image_decoder": [ "decode" ],
         "accessibility_autoclick": [ "ash:autoclick" ],
+        "preferences": [ "preferences_manager" ],
         "ui": [ "ime_registrar" ]
       }
     },
diff --git a/chrome/browser/chromeos/login/easy_unlock/bootstrap_manager.cc b/chrome/browser/chromeos/login/easy_unlock/bootstrap_manager.cc
index b5c8f1a..d7d9c09 100644
--- a/chrome/browser/chromeos/login/easy_unlock/bootstrap_manager.cc
+++ b/chrome/browser/chromeos/login/easy_unlock/bootstrap_manager.cc
@@ -11,6 +11,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
+#include "components/signin/core/account_id/account_id.h"
 #include "components/user_manager/known_user.h"
 
 namespace chromeos {
@@ -66,7 +67,8 @@
     if (users->GetString(i, &current_user_email)) {
       delegate_->RemovePendingBootstrapUser(
           user_manager::known_user::GetAccountId(current_user_email,
-                                                 std::string() /* gaia_id */));
+                                                 std::string() /* id */,
+                                                 AccountType::UNKNOWN));
     }
   }
 
diff --git a/chrome/browser/chromeos/login/easy_unlock/bootstrap_user_context_initializer.cc b/chrome/browser/chromeos/login/easy_unlock/bootstrap_user_context_initializer.cc
index 0cee775..d2434ec 100644
--- a/chrome/browser/chromeos/login/easy_unlock/bootstrap_user_context_initializer.cc
+++ b/chrome/browser/chromeos/login/easy_unlock/bootstrap_user_context_initializer.cc
@@ -178,7 +178,7 @@
   }
 
   user_context_.SetAccountId(user_manager::known_user::GetAccountId(
-      user_manager::CanonicalizeUserID(email), gaia_id));
+      user_manager::CanonicalizeUserID(email), gaia_id, AccountType::GOOGLE));
   StartCheckExistingKeys();
 }
 
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager.cc
index 3e64433..617a3be 100644
--- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager.cc
+++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager.cc
@@ -337,11 +337,10 @@
   // but there should be at most one such task at a time.
   get_tpm_slot_weak_ptr_factory_.InvalidateWeakPtrs();
 
-  // This task interacts with the TPM, hence WithFileIO() and WithWait().
+  // This task interacts with the TPM, hence MayBlock().
   base::PostTaskWithTraits(
-      FROM_HERE,
-      base::TaskTraits().WithFileIO().WithWait().WithShutdownBehavior(
-          base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN),
+      FROM_HERE, base::TaskTraits().MayBlock().WithShutdownBehavior(
+                     base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN),
       base::Bind(&CreateTpmKeyPairOnWorkerThread, base::Passed(&system_slot),
                  public_key, base::ThreadTaskRunnerHandle::Get(),
                  base::Bind(&EasyUnlockTpmKeyManager::OnTpmKeyCreated,
@@ -355,11 +354,10 @@
     crypto::ScopedPK11Slot system_slot) {
   CHECK(system_slot);
 
-  // This task interacts with the TPM, hence WithFileIO() and WithWait().
+  // This task interacts with the TPM, hence MayBlock().
   base::PostTaskWithTraits(
-      FROM_HERE,
-      base::TaskTraits().WithFileIO().WithWait().WithShutdownBehavior(
-          base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN),
+      FROM_HERE, base::TaskTraits().MayBlock().WithShutdownBehavior(
+                     base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN),
       base::Bind(&SignDataOnWorkerThread, base::Passed(&system_slot),
                  public_key, data, base::ThreadTaskRunnerHandle::Get(),
                  base::Bind(&EasyUnlockTpmKeyManager::OnDataSigned,
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_factory.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_factory.cc
index cdce1cf1..04a96fb5 100644
--- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_factory.cc
+++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_factory.cc
@@ -40,8 +40,9 @@
 EasyUnlockTpmKeyManager* EasyUnlockTpmKeyManagerFactory::GetForUser(
     const std::string& user_id) {
   user_manager::UserManager* user_manager = user_manager::UserManager::Get();
-  const user_manager::User* user = user_manager->FindUser(
-      user_manager::known_user::GetAccountId(user_id, std::string()));
+  const user_manager::User* user =
+      user_manager->FindUser(user_manager::known_user::GetAccountId(
+          user_id, std::string() /* id */, AccountType::UNKNOWN));
   if (!user)
     return NULL;
   Profile* profile = chromeos::ProfileHelper::Get()->GetProfileByUser(user);
diff --git a/chrome/browser/chromeos/login/screens/chrome_user_selection_screen.cc b/chrome/browser/chromeos/login/screens/chrome_user_selection_screen.cc
index 9362462..affc58b 100644
--- a/chrome/browser/chromeos/login/screens/chrome_user_selection_screen.cc
+++ b/chrome/browser/chromeos/login/screens/chrome_user_selection_screen.cc
@@ -85,8 +85,8 @@
 
 void ChromeUserSelectionScreen::CheckForPublicSessionDisplayNameChange(
     policy::DeviceLocalAccountPolicyBroker* broker) {
-  const AccountId& account_id =
-      user_manager::known_user::GetAccountId(broker->user_id(), std::string());
+  const AccountId& account_id = user_manager::known_user::GetAccountId(
+      broker->user_id(), std::string() /* id */, AccountType::UNKNOWN);
   DCHECK(account_id.is_valid());
   const std::string& display_name = broker->GetDisplayName();
   if (display_name == public_session_display_names_[account_id])
@@ -116,8 +116,8 @@
 
 void ChromeUserSelectionScreen::CheckForPublicSessionLocalePolicyChange(
     policy::DeviceLocalAccountPolicyBroker* broker) {
-  const AccountId& account_id =
-      user_manager::known_user::GetAccountId(broker->user_id(), std::string());
+  const AccountId& account_id = user_manager::known_user::GetAccountId(
+      broker->user_id(), std::string() /* id */, AccountType::UNKNOWN);
   DCHECK(account_id.is_valid());
   const policy::PolicyMap::Entry* entry =
       broker->core()->store()->policy_map().Get(policy::key::kSessionLocales);
diff --git a/chrome/browser/chromeos/login/screens/user_selection_screen.cc b/chrome/browser/chromeos/login/screens/user_selection_screen.cc
index dedfd7d..a3e89b2c 100644
--- a/chrome/browser/chromeos/login/screens/user_selection_screen.cc
+++ b/chrome/browser/chromeos/login/screens/user_selection_screen.cc
@@ -372,8 +372,8 @@
   std::string owner_email;
   chromeos::CrosSettings::Get()->GetString(chromeos::kDeviceOwner,
                                            &owner_email);
-  const AccountId owner =
-      user_manager::known_user::GetAccountId(owner_email, std::string());
+  const AccountId owner = user_manager::known_user::GetAccountId(
+      owner_email, std::string() /* id */, AccountType::UNKNOWN);
 
   policy::BrowserPolicyConnectorChromeOS* connector =
       g_browser_process->platform_part()->browser_policy_connector_chromeos();
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index 581a1c1..90a64d7c 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -898,11 +898,7 @@
 }
 
 void UserSessionManager::StoreUserContextDataBeforeProfileIsCreated() {
-  // Store obfuscated GAIA ID.
-  if (!user_context_.GetGaiaID().empty()) {
-    user_manager::known_user::UpdateGaiaID(user_context_.GetAccountId(),
-                                           user_context_.GetGaiaID());
-  }
+  user_manager::known_user::UpdateId(user_context_.GetAccountId());
 }
 
 void UserSessionManager::StartCrosSession() {
diff --git a/chrome/browser/chromeos/login/supervised/supervised_user_authenticator.cc b/chrome/browser/chromeos/login/supervised/supervised_user_authenticator.cc
index 7cbad7c..bca16cf0 100644
--- a/chrome/browser/chromeos/login/supervised/supervised_user_authenticator.cc
+++ b/chrome/browser/chromeos/login/supervised/supervised_user_authenticator.cc
@@ -70,7 +70,7 @@
   Key key(attempt->password);
   key.Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF, system_salt);
   const AccountId account_id = user_manager::known_user::GetAccountId(
-      attempt->username, std::string() /* gaia_id */);
+      attempt->username, std::string() /* id */, AccountType::UNKNOWN);
   const cryptohome::Identification cryptohome_id(account_id);
   cryptohome::AsyncMethodCaller::GetInstance()->AsyncMount(
       cryptohome_id, key.GetSecret(), flags,
@@ -95,7 +95,7 @@
   Key master_key(plain_text_master_key);
   master_key.Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF, system_salt);
   const AccountId account_id = user_manager::known_user::GetAccountId(
-      attempt->username, std::string() /* gaia_id */);
+      attempt->username, std::string() /* id */, AccountType::UNKNOWN);
   cryptohome::AsyncMethodCaller::GetInstance()->AsyncAddKey(
       cryptohome::Identification(account_id), user_key.GetSecret(),
       master_key.GetSecret(),
diff --git a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
index c7c0dba2..c72843a6 100644
--- a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
@@ -500,8 +500,8 @@
 
 void ChromeUserManagerImpl::OnExternalDataSet(const std::string& policy,
                                               const std::string& user_id) {
-  const AccountId account_id =
-      user_manager::known_user::GetAccountId(user_id, std::string());
+  const AccountId account_id = user_manager::known_user::GetAccountId(
+      user_id, std::string() /* id */, AccountType::UNKNOWN);
   if (policy == policy::key::kUserAvatarImage)
     GetUserImageManager(account_id)->OnExternalDataSet(policy);
   else if (policy == policy::key::kWallpaperImage)
@@ -512,8 +512,8 @@
 
 void ChromeUserManagerImpl::OnExternalDataCleared(const std::string& policy,
                                                   const std::string& user_id) {
-  const AccountId account_id =
-      user_manager::known_user::GetAccountId(user_id, std::string());
+  const AccountId account_id = user_manager::known_user::GetAccountId(
+      user_id, std::string() /* id */, AccountType::UNKNOWN);
   if (policy == policy::key::kUserAvatarImage)
     GetUserImageManager(account_id)->OnExternalDataCleared(policy);
   else if (policy == policy::key::kWallpaperImage)
@@ -526,8 +526,8 @@
     const std::string& policy,
     const std::string& user_id,
     std::unique_ptr<std::string> data) {
-  const AccountId account_id =
-      user_manager::known_user::GetAccountId(user_id, std::string());
+  const AccountId account_id = user_manager::known_user::GetAccountId(
+      user_id, std::string() /* id */, AccountType::UNKNOWN);
   if (policy == policy::key::kUserAvatarImage)
     GetUserImageManager(account_id)
         ->OnExternalDataFetched(policy, std::move(data));
@@ -539,8 +539,8 @@
 }
 
 void ChromeUserManagerImpl::OnPolicyUpdated(const std::string& user_id) {
-  const AccountId account_id =
-      user_manager::known_user::GetAccountId(user_id, std::string());
+  const AccountId account_id = user_manager::known_user::GetAccountId(
+      user_id, std::string() /* id */, AccountType::UNKNOWN);
   const user_manager::User* user = FindUser(account_id);
   if (!user || user->GetType() != user_manager::USER_TYPE_PUBLIC_ACCOUNT)
     return;
@@ -1216,8 +1216,8 @@
 void ChromeUserManagerImpl::SetUserAffiliation(
     const std::string& user_email,
     const AffiliationIDSet& user_affiliation_ids) {
-  const AccountId& account_id =
-      user_manager::known_user::GetAccountId(user_email, std::string());
+  const AccountId& account_id = user_manager::known_user::GetAccountId(
+      user_email, std::string() /* id */, AccountType::UNKNOWN);
   user_manager::User* user = FindUserAndModify(account_id);
 
   if (user) {
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win.cc b/chrome/browser/component_updater/sw_reporter_installer_win.cc
index 3424141..670e72d 100644
--- a/chrome/browser/component_updater/sw_reporter_installer_win.cc
+++ b/chrome/browser/component_updater/sw_reporter_installer_win.cc
@@ -136,13 +136,15 @@
       FROM_HERE, base::ThreadTaskRunnerHandle::Get(),
       base::Bind(&safe_browsing::RunSwReporters, invocations, version,
                  base::ThreadTaskRunnerHandle::Get(),
-                 // Runs LaunchAndWaitForExit() (srt_fetcher_win.cc).
+                 // Runs LaunchAndWaitForExit() which creates (MayBlock()) and
+                 // joins (WithSyncPrimitives()) a process.
                  base::CreateTaskRunnerWithTraits(
                      base::TaskTraits()
                          .WithShutdownBehavior(
                              base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN)
                          .WithPriority(base::TaskPriority::BACKGROUND)
-                         .WithWait())));
+                         .MayBlock()
+                         .WithSyncPrimitives())));
 }
 
 // Ensures |str| contains only alphanumeric characters and characters from
diff --git a/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc b/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc
index 2b4a13b..d065de4 100644
--- a/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc
+++ b/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc
@@ -34,10 +34,11 @@
 #include "components/ntp_snippets/features.h"
 #include "components/ntp_snippets/ntp_snippets_constants.h"
 #include "components/ntp_snippets/remote/ntp_snippets_fetcher.h"
-#include "components/ntp_snippets/remote/ntp_snippets_scheduler.h"
+#include "components/ntp_snippets/remote/persistent_scheduler.h"
 #include "components/ntp_snippets/remote/remote_suggestions_database.h"
-#include "components/ntp_snippets/remote/remote_suggestions_provider.h"
+#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
 #include "components/ntp_snippets/remote/remote_suggestions_status_service.h"
+#include "components/ntp_snippets/remote/scheduling_remote_suggestions_provider.h"
 #include "components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.h"
 #include "components/ntp_snippets/sessions/tab_delegate_sync_adapter.h"
 #include "components/prefs/pref_service.h"
@@ -78,10 +79,11 @@
 using ntp_snippets::ContentSuggestionsService;
 using ntp_snippets::ForeignSessionsSuggestionsProvider;
 using ntp_snippets::NTPSnippetsFetcher;
-using ntp_snippets::NTPSnippetsScheduler;
+using ntp_snippets::PersistentScheduler;
 using ntp_snippets::RemoteSuggestionsDatabase;
-using ntp_snippets::RemoteSuggestionsProvider;
+using ntp_snippets::RemoteSuggestionsProviderImpl;
 using ntp_snippets::RemoteSuggestionsStatusService;
+using ntp_snippets::SchedulingRemoteSuggestionsProvider;
 using ntp_snippets::TabDelegateSyncAdapter;
 using suggestions::ImageDecoderImpl;
 using syncer::SyncService;
@@ -147,10 +149,6 @@
       content::BrowserContext::GetDefaultStoragePartition(profile)
           ->GetURLRequestContext();
 
-  NTPSnippetsScheduler* scheduler = nullptr;
-#if defined(OS_ANDROID)
-  scheduler = NTPSnippetsLauncher::Get();
-#endif  // OS_ANDROID
   base::FilePath database_dir(
       profile->GetPath().Append(ntp_snippets::kDatabaseFolder));
   scoped_refptr<base::SequencedTaskRunner> task_runner =
@@ -160,9 +158,9 @@
               base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
   bool is_stable_channel =
       chrome::GetChannel() == version_info::Channel::STABLE;
-  auto provider = base::MakeUnique<RemoteSuggestionsProvider>(
+  auto provider = base::MakeUnique<RemoteSuggestionsProviderImpl>(
       service, pref_service, g_browser_process->GetApplicationLocale(),
-      service->category_ranker(), service->user_classifier(), scheduler,
+      service->category_ranker(),
       base::MakeUnique<NTPSnippetsFetcher>(
           signin_manager, token_service, request_context, pref_service,
           language_model, base::Bind(&safe_json::SafeJsonParser::Parse),
@@ -175,8 +173,19 @@
       base::MakeUnique<RemoteSuggestionsDatabase>(database_dir, task_runner),
       base::MakeUnique<RemoteSuggestionsStatusService>(signin_manager,
                                                        pref_service));
-  service->set_ntp_snippets_service(provider.get());
-  service->RegisterProvider(std::move(provider));
+
+  PersistentScheduler* scheduler = nullptr;
+#if defined(OS_ANDROID)
+  scheduler = NTPSnippetsLauncher::Get();
+#endif  // OS_ANDROID
+
+  auto scheduling_provider =
+      base::MakeUnique<SchedulingRemoteSuggestionsProvider>(
+          service, std::move(provider), scheduler, service->user_classifier(),
+          pref_service);
+  service->set_remote_suggestions_provider(scheduling_provider.get());
+  service->set_remote_suggestions_scheduler(scheduling_provider.get());
+  service->RegisterProvider(std::move(scheduling_provider));
 }
 
 void RegisterForeignSessionsProvider(SyncService* sync_service,
diff --git a/chrome/browser/page_load_metrics/metrics_web_contents_observer.cc b/chrome/browser/page_load_metrics/metrics_web_contents_observer.cc
index 571ef10..b51abec 100644
--- a/chrome/browser/page_load_metrics/metrics_web_contents_observer.cc
+++ b/chrome/browser/page_load_metrics/metrics_web_contents_observer.cc
@@ -308,10 +308,11 @@
 }
 
 void MetricsWebContentsObserver::FlushMetricsOnAppEnterBackground() {
-  // Signal to observers that we've been backgrounded, in cases where the
-  // FlushMetricsOnAppEnterBackground callback gets invoked before the
-  // associated WasHidden callback.
-  WasHidden();
+  // Note that, while a call to FlushMetricsOnAppEnterBackground usually
+  // indicates that the app is about to be backgrounded, there are cases where
+  // the app may not end up getting backgrounded. Thus, we should not assume
+  // anything about foreground / background state of the associated tab as part
+  // of this method call.
 
   if (committed_load_)
     committed_load_->FlushMetricsOnAppEnterBackground();
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.cc
index 50fc141..50e1cdba 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.cc
@@ -132,6 +132,18 @@
   return STOP_OBSERVING;
 }
 
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+DataReductionProxyMetricsObserver::FlushMetricsOnAppEnterBackground(
+    const page_load_metrics::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& info) {
+  // FlushMetricsOnAppEnterBackground is invoked on Android in cases where the
+  // app is about to be backgrounded, as part of the Activity.onPause()
+  // flow. After this method is invoked, Chrome may be killed without further
+  // notification, so we send a pingback with data collected up to this point.
+  SendPingback(timing, info);
+  return STOP_OBSERVING;
+}
+
 void DataReductionProxyMetricsObserver::OnComplete(
     const page_load_metrics::PageLoadTiming& timing,
     const page_load_metrics::PageLoadExtraInfo& info) {
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.h b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.h
index 591e9e5..7a13346 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.h
@@ -59,6 +59,9 @@
   ObservePolicy OnHidden(
       const page_load_metrics::PageLoadTiming& timing,
       const page_load_metrics::PageLoadExtraInfo& info) override;
+  ObservePolicy FlushMetricsOnAppEnterBackground(
+      const page_load_metrics::PageLoadTiming& timing,
+      const page_load_metrics::PageLoadExtraInfo& info) override;
   void OnComplete(const page_load_metrics::PageLoadTiming& timing,
                   const page_load_metrics::PageLoadExtraInfo& info) override;
   void OnDomContentLoadedEventStart(
diff --git a/chrome/browser/password_manager/password_manager_util_win.cc b/chrome/browser/password_manager/password_manager_util_win.cc
index 7071a4f..43ceb84e 100644
--- a/chrome/browser/password_manager/password_manager_util_win.cc
+++ b/chrome/browser/password_manager/password_manager_util_win.cc
@@ -207,10 +207,11 @@
       new OsPasswordStatus(PASSWORD_STATUS_UNKNOWN));
   PasswordCheckPrefs* prefs_weak = prefs.get();
   OsPasswordStatus* status_weak = status.get();
+  // This task calls ::LogonUser(), hence MayBlock().
   base::PostTaskWithTraitsAndReply(
       FROM_HERE, base::TaskTraits()
                      .WithPriority(base::TaskPriority::BACKGROUND)
-                     .WithWait(),
+                     .MayBlock(),
       base::Bind(&GetOsPasswordStatusInternal, prefs_weak, status_weak),
       base::Bind(&ReplyOsPasswordStatus, base::Passed(&prefs),
                  base::Passed(&status)));
diff --git a/chrome/browser/prefs/DEPS b/chrome/browser/prefs/DEPS
index 5008dbb..66a793c3 100644
--- a/chrome/browser/prefs/DEPS
+++ b/chrome/browser/prefs/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
   "+components/sync/base/model_type.h",
+  "+services/preferences/public/interfaces",
 ]
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index a8dddc8..a87a1a4 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -79,8 +79,9 @@
 #include "components/network_time/network_time_tracker.h"
 #include "components/ntp_snippets/bookmarks/bookmark_suggestions_provider.h"
 #include "components/ntp_snippets/content_suggestions_service.h"
-#include "components/ntp_snippets/remote/remote_suggestions_provider.h"
+#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
 #include "components/ntp_snippets/remote/request_throttler.h"
+#include "components/ntp_snippets/remote/scheduling_remote_suggestions_provider.h"
 #include "components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.h"
 #include "components/ntp_snippets/user_classifier.h"
 #include "components/ntp_tiles/most_visited_sites.h"
@@ -495,11 +496,13 @@
   MediaStreamDevicesController::RegisterProfilePrefs(registry);
   NotifierStateTracker::RegisterProfilePrefs(registry);
   ntp_snippets::BookmarkSuggestionsProvider::RegisterProfilePrefs(registry);
+  ntp_snippets::ContentSuggestionsService::RegisterProfilePrefs(registry);
   ntp_snippets::ForeignSessionsSuggestionsProvider::RegisterProfilePrefs(
       registry);
-  ntp_snippets::RemoteSuggestionsProvider::RegisterProfilePrefs(registry);
-  ntp_snippets::ContentSuggestionsService::RegisterProfilePrefs(registry);
+  ntp_snippets::RemoteSuggestionsProviderImpl::RegisterProfilePrefs(registry);
   ntp_snippets::RequestThrottler::RegisterProfilePrefs(registry);
+  ntp_snippets::SchedulingRemoteSuggestionsProvider::RegisterProfilePrefs(
+      registry);
   ntp_snippets::UserClassifier::RegisterProfilePrefs(registry);
   ntp_tiles::MostVisitedSites::RegisterProfilePrefs(registry);
   password_bubble_experiment::RegisterPrefs(registry);
diff --git a/chrome/browser/prefs/preferences_connection_manager.cc b/chrome/browser/prefs/preferences_connection_manager.cc
new file mode 100644
index 0000000..8a97e55
--- /dev/null
+++ b/chrome/browser/prefs/preferences_connection_manager.cc
@@ -0,0 +1,105 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/preferences_connection_manager.h"
+
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/prefs/preferences_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
+#include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
+#include "services/service_manager/public/cpp/interface_registry.h"
+
+namespace {
+
+// Required singleton subclass to control dependencies between
+// PreferenceConnectionManager and the KeyedServiceShutdownNotifier.
+class ShutdownNotifierFactory
+    : public BrowserContextKeyedServiceShutdownNotifierFactory {
+ public:
+  static ShutdownNotifierFactory* GetInstance() {
+    return base::Singleton<ShutdownNotifierFactory>::get();
+  }
+
+ private:
+  friend struct base::DefaultSingletonTraits<ShutdownNotifierFactory>;
+
+  ShutdownNotifierFactory()
+      : BrowserContextKeyedServiceShutdownNotifierFactory(
+            "PreferencesConnectionManager") {
+    DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
+  }
+  ~ShutdownNotifierFactory() override {}
+
+  DISALLOW_COPY_AND_ASSIGN(ShutdownNotifierFactory);
+};
+
+}  // namespace
+
+PreferencesConnectionManager::PreferencesConnectionManager() {}
+
+PreferencesConnectionManager::~PreferencesConnectionManager() {}
+
+void PreferencesConnectionManager::OnConnectionError(
+    mojo::StrongBindingPtr<prefs::mojom::PreferencesManager> binding) {
+  if (!binding)
+    return;
+  for (auto it = std::begin(bindings_); it != std::end(bindings_); ++it) {
+    if (it->get() == binding.get()) {
+      bindings_.erase(it);
+      return;
+    }
+  }
+}
+
+void PreferencesConnectionManager::OnProfileDestroyed() {
+  for (auto& it : bindings_) {
+    // Shutdown any PreferenceManager that is still alive.
+    if (it)
+      it->Close();
+  }
+
+  profile_shutdown_notification_.reset();
+}
+
+void PreferencesConnectionManager::Create(
+    const service_manager::Identity& remote_identity,
+    prefs::mojom::PreferencesManagerRequest request) {
+  // Certain tests have no profiles to connect to, and static initializers
+  // which block the creation of test profiles.
+  if (!g_browser_process->profile_manager()->GetNumberOfProfiles())
+    return;
+
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+  mojo::StrongBindingPtr<prefs::mojom::PreferencesManager> binding =
+      mojo::MakeStrongBinding(base::MakeUnique<PreferencesManager>(profile),
+                              std::move(request));
+  // Copying the base::WeakPtr for future deletion.
+  binding->set_connection_error_handler(
+      base::Bind(&PreferencesConnectionManager::OnConnectionError,
+                 base::Unretained(this), binding));
+  bindings_.push_back(std::move(binding));
+}
+
+void PreferencesConnectionManager::OnStart() {
+  // Certain tests have no profiles to connect to, and static initializers
+  // which block the creation of test profiles.
+  if (!g_browser_process->profile_manager()->GetNumberOfProfiles())
+    return;
+
+  profile_shutdown_notification_ =
+      ShutdownNotifierFactory::GetInstance()
+          ->Get(ProfileManager::GetActiveUserProfile())
+          ->Subscribe(
+              base::Bind(&PreferencesConnectionManager::OnProfileDestroyed,
+                         base::Unretained(this)));
+}
+
+bool PreferencesConnectionManager::OnConnect(
+    const service_manager::ServiceInfo& remote_info,
+    service_manager::InterfaceRegistry* registry) {
+  registry->AddInterface<prefs::mojom::PreferencesManager>(this);
+  return true;
+}
diff --git a/chrome/browser/prefs/preferences_connection_manager.h b/chrome/browser/prefs/preferences_connection_manager.h
new file mode 100644
index 0000000..0f9e8df
--- /dev/null
+++ b/chrome/browser/prefs/preferences_connection_manager.h
@@ -0,0 +1,62 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_PREFERENCES_CONNECTION_MANAGER_H_
+#define CHROME_BROWSER_PREFS_PREFERENCES_CONNECTION_MANAGER_H_
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+#include "services/service_manager/public/cpp/interface_factory.h"
+#include "services/service_manager/public/cpp/service.h"
+
+// Handles all incoming prefs::mojom::PreferenceManagerRequest, providing a
+// separate PreferencesManager per connection request.
+//
+// Additionally monitors system shutdown to clean up connections to PrefService.
+//
+// TODO(jonross): Observe profile switching and update PreferenceManager
+// connections.
+class PreferencesConnectionManager
+    : public NON_EXPORTED_BASE(
+          service_manager::InterfaceFactory<prefs::mojom::PreferencesManager>),
+      public NON_EXPORTED_BASE(service_manager::Service) {
+ public:
+  PreferencesConnectionManager();
+  ~PreferencesConnectionManager() override;
+
+ private:
+  // mojo::StrongBinding callback:
+  void OnConnectionError(
+      mojo::StrongBindingPtr<prefs::mojom::PreferencesManager> binding);
+
+  // KeyedServiceShutdownNotifier::Subscription callback. Used to cleanup when
+  // the active PrefService is being destroyed.
+  void OnProfileDestroyed();
+
+  // service_manager::InterfaceFactory<PreferencesManager>:
+  void Create(const service_manager::Identity& remote_identity,
+              prefs::mojom::PreferencesManagerRequest request) override;
+
+  // service_manager::Service:
+  void OnStart() override;
+  bool OnConnect(const service_manager::ServiceInfo& remote_info,
+                 service_manager::InterfaceRegistry* registry) override;
+
+  // Bindings that automatically cleanup during connection errors.
+  std::vector<mojo::StrongBindingPtr<prefs::mojom::PreferencesManager>>
+      bindings_;
+
+  // Observes shutdown, when PrefService is being destroyed.
+  std::unique_ptr<KeyedServiceShutdownNotifier::Subscription>
+      profile_shutdown_notification_;
+
+  DISALLOW_COPY_AND_ASSIGN(PreferencesConnectionManager);
+};
+
+#endif  // CHROME_BROWSER_PREFS_PREFERENCES_CONNECTION_MANAGER_H_
diff --git a/chrome/browser/prefs/preferences_manager.cc b/chrome/browser/prefs/preferences_manager.cc
new file mode 100644
index 0000000..d3fe49d1
--- /dev/null
+++ b/chrome/browser/prefs/preferences_manager.cc
@@ -0,0 +1,89 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/preferences_manager.h"
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/values.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+
+PreferencesManager::PreferencesManager(Profile* profile)
+    : preferences_change_registrar_(new PrefChangeRegistrar),
+      setting_preferences_(false) {
+  DCHECK(profile);
+  service_ = profile->GetPrefs();
+  preferences_change_registrar_->Init(service_);
+}
+
+PreferencesManager::~PreferencesManager() {}
+
+void PreferencesManager::PreferenceChanged(const std::string& preference_name) {
+  if (setting_preferences_)
+    return;
+  const PrefService::Preference* pref =
+      service_->FindPreference(preference_name);
+  std::unique_ptr<base::DictionaryValue> dictionary =
+      base::MakeUnique<base::DictionaryValue>();
+  dictionary->Set(preference_name, pref->GetValue()->CreateDeepCopy());
+  client_->OnPreferencesChanged(std::move(dictionary));
+}
+
+void PreferencesManager::AddObserver(
+    prefs::mojom::PreferencesObserverPtr client) {
+  // TODO(jonross): once service_manager::Connector supports enforcing two-way
+  // binding at connection time, update PreferencesManager to use that approach.
+  // After which enforcing bind checks will not be needed (crbug.com/674140)
+  client_ = std::move(client);
+}
+
+void PreferencesManager::SetPreferences(
+    std::unique_ptr<base::DictionaryValue> preferences) {
+  if (!client_.is_bound())
+    return;
+  DCHECK(!setting_preferences_);
+  // We ignore preference changes caused by us.
+  base::AutoReset<bool> setting_preferences(&setting_preferences_, true);
+  for (base::DictionaryValue::Iterator it(*preferences); !it.IsAtEnd();
+       it.Advance()) {
+    if (!preferences_change_registrar_->IsObserved(it.key()))
+      continue;
+    const PrefService::Preference* pref = service_->FindPreference(it.key());
+    if (!pref) {
+      DLOG(ERROR) << "Preference " << it.key() << " not found.\n";
+      continue;
+    }
+    if (it.value().Equals(pref->GetValue()))
+      continue;
+    service_->Set(it.key(), it.value());
+  }
+}
+
+void PreferencesManager::Subscribe(
+    const std::vector<std::string>& preferences) {
+  if (!client_.is_bound())
+    return;
+  std::unique_ptr<base::DictionaryValue> dictionary =
+      base::MakeUnique<base::DictionaryValue>();
+  for (auto& it : preferences) {
+    const PrefService::Preference* pref = service_->FindPreference(it);
+    if (!pref) {
+      DLOG(ERROR) << "Preference " << it << " not found.\n";
+      continue;
+    }
+    // PreferenceManager lifetime is managed by a mojo::StrongBindingPtr owned
+    // by PreferenceConnectionManager. It will outlive
+    // |preferences_change_registrar_| which it owns.
+    preferences_change_registrar_->Add(
+        it, base::Bind(&PreferencesManager::PreferenceChanged,
+                       base::Unretained(this)));
+    dictionary->Set(it, pref->GetValue()->CreateDeepCopy());
+  }
+
+  if (dictionary->empty())
+    return;
+  client_->OnPreferencesChanged(std::move(dictionary));
+}
diff --git a/chrome/browser/prefs/preferences_manager.h b/chrome/browser/prefs/preferences_manager.h
new file mode 100644
index 0000000..9ffc67a
--- /dev/null
+++ b/chrome/browser/prefs/preferences_manager.h
@@ -0,0 +1,55 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_PREFERENCES_MANAGER_H_
+#define CHROME_BROWSER_PREFS_PREFERENCES_MANAGER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+
+namespace test {
+class PreferencesManagerTest;
+}
+
+class PrefChangeRegistrar;
+class PrefService;
+class Profile;
+
+// Implementation of prefs::mojom::PreferencesManager that accepts a single
+// prefs::mojom::PreferencesObserver.
+//
+// After calling AddObserver PreferencesManager will begin observing changes to
+// the requested preferences, notifying the client of all changes.
+class PreferencesManager : public prefs::mojom::PreferencesManager {
+ public:
+  explicit PreferencesManager(Profile* profile);
+  ~PreferencesManager() override;
+
+ private:
+  friend class test::PreferencesManagerTest;
+
+  // PrefChangeRegistrar::NamedChangeCallback:
+  void PreferenceChanged(const std::string& preference_name);
+
+  // mojom::PreferencesManager:
+  void AddObserver(prefs::mojom::PreferencesObserverPtr client) override;
+  void SetPreferences(
+      std::unique_ptr<base::DictionaryValue> preferences) override;
+  void Subscribe(const std::vector<std::string>& preferences) override;
+
+  // Tracks the desired preferences, and listens for updates.
+  std::unique_ptr<PrefChangeRegistrar> preferences_change_registrar_;
+  prefs::mojom::PreferencesObserverPtr client_;
+  PrefService* service_;
+
+  // Used to prevent notifying |client_| of changes caused by it calling
+  // SetPreferences.
+  bool setting_preferences_;
+
+  DISALLOW_COPY_AND_ASSIGN(PreferencesManager);
+};
+
+#endif  // CHROME_BROWSER_PREFS_PREFERENCES_MANAGER_H_
diff --git a/chrome/browser/prefs/preferences_manager_unittest.cc b/chrome/browser/prefs/preferences_manager_unittest.cc
new file mode 100644
index 0000000..ea0487e
--- /dev/null
+++ b/chrome/browser/prefs/preferences_manager_unittest.cc
@@ -0,0 +1,306 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/preferences_manager.h"
+
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/prefs/browser_prefs.h"
+#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/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Test implementation of prefs::mojom::PreferencesObserver which just tracks
+// calls to OnPreferencesChanged.
+class TestPreferencesObserver : public prefs::mojom::PreferencesObserver {
+ public:
+  TestPreferencesObserver(
+      mojo::InterfaceRequest<prefs::mojom::PreferencesObserver> request)
+      : on_preferences_changed_called_(false),
+        binding_(this, std::move(request)) {}
+  ~TestPreferencesObserver() override {}
+
+  // Returns true is |key| was in the last set of preferences changed.
+  bool KeyReceived(const std::string& key);
+
+  // Clears the values set from the last OnPreferencesChanged.
+  void Reset();
+
+  bool on_preferences_changed_called() {
+    return on_preferences_changed_called_;
+  }
+
+  const base::Value* on_preferences_changed_values() {
+    return on_preferences_changed_values_.get();
+  }
+
+ private:
+  // prefs::mojom::PreferencesObserver:
+  void OnPreferencesChanged(
+      std::unique_ptr<base::DictionaryValue> preferences) override {
+    on_preferences_changed_called_ = true;
+    on_preferences_changed_values_ = std::move(preferences);
+  }
+
+  bool on_preferences_changed_called_;
+  std::unique_ptr<base::Value> on_preferences_changed_values_;
+
+  mojo::Binding<PreferencesObserver> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestPreferencesObserver);
+};
+
+bool TestPreferencesObserver::KeyReceived(const std::string& key) {
+  base::DictionaryValue* dictionary = nullptr;
+  on_preferences_changed_values_->GetAsDictionary(&dictionary);
+  return dictionary->HasKey(key);
+}
+
+void TestPreferencesObserver::Reset() {
+  on_preferences_changed_called_ = false;
+  on_preferences_changed_values_.reset();
+}
+
+}  // namespace
+
+namespace test {
+class PreferencesManagerTest : public testing::Test {
+ public:
+  PreferencesManagerTest();
+  ~PreferencesManagerTest() override;
+
+  // Initializes the connection between |observer_| and |manager_|, subscribing
+  // to changes to |preferences|.
+  void InitObserver(const std::vector<std::string>& preferences);
+
+  // Initializes a preference with |registry_| with a default |value|.
+  void InitPreference(const std::string& key, int value);
+
+  // Has |manager_| update the PrefStore with |preferences|.
+  void SetPreferences(std::unique_ptr<base::DictionaryValue> preferences);
+
+  PreferencesManager* manager() { return manager_.get(); }
+  TestPreferencesObserver* observer() { return observer_.get(); }
+  PrefChangeRegistrar* preferences_change_registrar() {
+    return manager_->preferences_change_registrar_.get();
+  }
+  TestingProfile* profile() { return profile_; }
+  user_prefs::PrefRegistrySyncable* registry() { return registry_; }
+  PrefService* service() { return profile_->GetPrefs(); }
+
+  // testing::Test:
+  void SetUp() override;
+  void TearDown() override;
+
+ private:
+  // Sets up threads needed for |testing_profile_manager_|
+  content::TestBrowserThreadBundle thread_bundle_;
+
+  // Handles creation of profiles for testing.
+  TestingProfileManager testing_profile_manager_;
+
+  // Not owned
+  TestingProfile* profile_;
+  user_prefs::PrefRegistrySyncable* registry_;
+
+  prefs::mojom::PreferencesObserverPtr proxy_;
+  std::unique_ptr<TestPreferencesObserver> observer_;
+  std::unique_ptr<PreferencesManager> manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(PreferencesManagerTest);
+};
+
+PreferencesManagerTest::PreferencesManagerTest()
+    : testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {}
+
+PreferencesManagerTest::~PreferencesManagerTest() {}
+
+void PreferencesManagerTest::InitObserver(
+    const std::vector<std::string>& preferences) {
+  manager_->Subscribe(preferences);
+  base::RunLoop().RunUntilIdle();
+}
+
+void PreferencesManagerTest::InitPreference(const std::string& key, int value) {
+  registry_->RegisterIntegerPref(key, value);
+  base::FundamentalValue fundamental_value(value);
+  profile_->GetPrefs()->Set(key, fundamental_value);
+}
+
+void PreferencesManagerTest::SetPreferences(
+    std::unique_ptr<base::DictionaryValue> preferences) {
+  manager_->SetPreferences(std::move(preferences));
+  base::RunLoop().RunUntilIdle();
+}
+
+void PreferencesManagerTest::SetUp() {
+  ASSERT_TRUE(testing_profile_manager_.SetUp());
+
+  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> service(
+      base::MakeUnique<sync_preferences::TestingPrefServiceSyncable>());
+  registry_ = service->registry();
+  chrome::RegisterUserProfilePrefs(registry_);
+
+  const std::string kName = "navi";
+  profile_ = testing_profile_manager_.CreateTestingProfile(
+      kName, std::move(service), base::UTF8ToUTF16(kName), 0, std::string(),
+      TestingProfile::TestingFactories());
+  ASSERT_NE(nullptr, profile_->GetPrefs());
+
+  observer_.reset(new TestPreferencesObserver(mojo::MakeRequest(&proxy_)));
+  manager_ = base::MakeUnique<PreferencesManager>(profile_);
+  ASSERT_TRUE(manager_->preferences_change_registrar_->IsEmpty());
+  manager_->AddObserver(std::move(proxy_));
+}
+
+void PreferencesManagerTest::TearDown() {
+  testing_profile_manager_.DeleteAllTestingProfiles();
+}
+
+// Tests that when the PrefService is empty that no subscriptions are made.
+TEST_F(PreferencesManagerTest, EmptyService) {
+  const std::string kKey = "hey";
+  std::vector<std::string> preferences;
+  preferences.push_back(kKey);
+  InitObserver(preferences);
+  EXPECT_FALSE(preferences_change_registrar()->IsObserved(kKey));
+  EXPECT_FALSE(observer()->on_preferences_changed_called());
+}
+
+// Tests that when the PrefService has the desired key, that a subscription is
+// setup and that the PreferenceObserver is notified.
+TEST_F(PreferencesManagerTest, ServiceHasValues) {
+  const std::string kKey = "hey";
+  const int kValue = 42;
+  InitPreference(kKey, kValue);
+
+  std::vector<std::string> preferences;
+  preferences.push_back(kKey);
+  InitObserver(preferences);
+  EXPECT_TRUE(preferences_change_registrar()->IsObserved(kKey));
+  EXPECT_TRUE(observer()->on_preferences_changed_called());
+  EXPECT_TRUE(observer()->KeyReceived(kKey));
+}
+
+// Tests that mulitple keys can be subscribed to.
+TEST_F(PreferencesManagerTest, MultipleSubscriptions) {
+  const std::string kKey1 = "hey";
+  const int kValue1 = 42;
+  InitPreference(kKey1, kValue1);
+
+  const std::string kKey2 = "listen";
+  const int kValue2 = 9001;
+  InitPreference(kKey2, kValue2);
+
+  std::vector<std::string> preferences;
+  preferences.push_back(kKey1);
+  preferences.push_back(kKey2);
+  InitObserver(preferences);
+  EXPECT_TRUE(preferences_change_registrar()->IsObserved(kKey1));
+  EXPECT_TRUE(preferences_change_registrar()->IsObserved(kKey2));
+  EXPECT_TRUE(observer()->KeyReceived(kKey1));
+  EXPECT_TRUE(observer()->KeyReceived(kKey2));
+}
+
+// Tests that when all keys are not in the PrefService that subscriptions are
+// set for the available key.
+TEST_F(PreferencesManagerTest, PartialSubsriptionAvailable) {
+  const std::string kKey1 = "hey";
+  const int kValue1 = 42;
+  InitPreference(kKey1, kValue1);
+
+  const std::string kKey2 = "listen";
+  std::vector<std::string> preferences;
+  preferences.push_back(kKey1);
+  preferences.push_back(kKey2);
+  InitObserver(preferences);
+  EXPECT_TRUE(preferences_change_registrar()->IsObserved(kKey1));
+  EXPECT_FALSE(preferences_change_registrar()->IsObserved(kKey2));
+  EXPECT_TRUE(observer()->KeyReceived(kKey1));
+  EXPECT_FALSE(observer()->KeyReceived(kKey2));
+}
+
+// Tests that when a preference is changed that the PreferenceObserver is
+// notified.
+TEST_F(PreferencesManagerTest, PreferenceChanged) {
+  const std::string kKey = "hey";
+  const int kValue = 42;
+  InitPreference(kKey, kValue);
+
+  std::vector<std::string> preferences;
+  preferences.push_back(kKey);
+  InitObserver(preferences);
+  observer()->Reset();
+
+  const int kNewValue = 1337;
+  service()->SetInteger(kKey, kNewValue);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(observer()->on_preferences_changed_called());
+  const base::Value* values = observer()->on_preferences_changed_values();
+  const base::DictionaryValue* dictionary = nullptr;
+  values->GetAsDictionary(&dictionary);
+  int result = 0;
+  dictionary->GetInteger(kKey, &result);
+  EXPECT_EQ(kNewValue, result);
+}
+
+// Tests that when a non subscribed preference is changed that the
+// PreferenceObserver is not notified.
+TEST_F(PreferencesManagerTest, UnrelatedPreferenceChanged) {
+  const std::string kKey1 = "hey";
+  const int kValue1 = 42;
+  InitPreference(kKey1, kValue1);
+
+  const std::string kKey2 = "listen";
+  const int kValue2 = 9001;
+  InitPreference(kKey2, kValue2);
+
+  std::vector<std::string> preferences;
+  preferences.push_back(kKey1);
+  InitObserver(preferences);
+  observer()->Reset();
+
+  const int kNewValue = 1337;
+  service()->SetInteger(kKey2, kNewValue);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(observer()->on_preferences_changed_called());
+}
+
+// Tests that when the PreferenceManager updates a preference that the
+// PreferenceObserver is not notified.
+TEST_F(PreferencesManagerTest, NoNotificationsForSelfChange) {
+  const std::string kKey = "hey";
+  const int kValue = 42;
+  InitPreference(kKey, kValue);
+
+  std::vector<std::string> preferences;
+  preferences.push_back(kKey);
+  InitObserver(preferences);
+  observer()->Reset();
+
+  const int kNewValue = 1337;
+  std::unique_ptr<base::DictionaryValue> dictionary =
+      base::MakeUnique<base::DictionaryValue>();
+  dictionary->SetInteger(kKey, kNewValue);
+  SetPreferences(std::move(dictionary));
+
+  EXPECT_FALSE(observer()->on_preferences_changed_called());
+  EXPECT_EQ(kNewValue, service()->GetInteger(kKey));
+}
+
+}  // namespace test
diff --git a/chrome/browser/prefs/preferences_manifest.json b/chrome/browser/prefs/preferences_manifest.json
new file mode 100644
index 0000000..37b790ad
--- /dev/null
+++ b/chrome/browser/prefs/preferences_manifest.json
@@ -0,0 +1,13 @@
+{
+  "name": "preferences",
+  "display_name": "Preferences",
+  "interface_provider_specs": {
+    "service_manager:connector": {
+      "provides": {
+        "preferences_manager": [ "prefs::mojom::PreferencesManager" ]
+      },
+      "requires": {
+      }
+    }
+  }
+}
diff --git a/chrome/browser/previews/previews_infobar_tab_helper.cc b/chrome/browser/previews/previews_infobar_tab_helper.cc
index a3b8de9..976963d7 100644
--- a/chrome/browser/previews/previews_infobar_tab_helper.cc
+++ b/chrome/browser/previews/previews_infobar_tab_helper.cc
@@ -55,8 +55,7 @@
 PreviewsInfoBarTabHelper::PreviewsInfoBarTabHelper(
     content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
-      displayed_preview_infobar_(false),
-      is_showing_offline_preview_(false) {
+      displayed_preview_infobar_(false){
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
 }
 
@@ -66,7 +65,6 @@
   if (!navigation_handle->IsInMainFrame() ||
       !navigation_handle->HasCommitted() || navigation_handle->IsSamePage())
     return;
-  is_showing_offline_preview_ = false;
   displayed_preview_infobar_ = false;
 
 #if BUILDFLAG(ANDROID_JAVA_UI)
@@ -82,7 +80,6 @@
         data_reduction_proxy_settings =
             DataReductionProxyChromeSettingsFactory::GetForBrowserContext(
                 web_contents()->GetBrowserContext());
-    is_showing_offline_preview_ = true;
     PreviewsInfoBarDelegate::Create(
         web_contents(), PreviewsInfoBarDelegate::OFFLINE,
         data_reduction_proxy_settings &&
diff --git a/chrome/browser/previews/previews_infobar_tab_helper.h b/chrome/browser/previews/previews_infobar_tab_helper.h
index 8edae9cf..512726f1 100644
--- a/chrome/browser/previews/previews_infobar_tab_helper.h
+++ b/chrome/browser/previews/previews_infobar_tab_helper.h
@@ -29,11 +29,6 @@
     displayed_preview_infobar_ = displayed;
   }
 
-  // Whether an offline preview has been shown for this page.
-  bool is_showing_offline_preview() const {
-    return is_showing_offline_preview_;
-  }
-
  private:
   friend class content::WebContentsUserData<PreviewsInfoBarTabHelper>;
   friend class PreviewsInfoBarTabHelperUnitTest;
@@ -47,9 +42,6 @@
   // True if the InfoBar for a preview has been shown for the page.
   bool displayed_preview_infobar_;
 
-  // Whether an offline preview has been shown for this page.
-  bool is_showing_offline_preview_;
-
   DISALLOW_COPY_AND_ASSIGN(PreviewsInfoBarTabHelper);
 };
 
diff --git a/chrome/browser/resources/settings/site_settings/site_list.html b/chrome/browser/resources/settings/site_settings/site_list.html
index 99f1a761b..0c18ca9 100644
--- a/chrome/browser/resources/settings/site_settings/site_list.html
+++ b/chrome/browser/resources/settings/site_settings/site_list.html
@@ -82,7 +82,7 @@
             </template>
 
             <paper-icon-button id="dots" icon="cr:more-vert"
-                hidden="[[isExceptionControlled_(item.source)]]"
+                hidden="[[isActionMenuHidden_(item.source)]]"
                 on-tap="onShowActionMenuTap_">
             </paper-icon-button>
             <template is="dom-if" if="[[enableSiteSettings_]]">
diff --git a/chrome/browser/resources/settings/site_settings/site_list.js b/chrome/browser/resources/settings/site_settings/site_list.js
index 9564724e..bffe5019 100644
--- a/chrome/browser/resources/settings/site_settings/site_list.js
+++ b/chrome/browser/resources/settings/site_settings/site_list.js
@@ -202,8 +202,8 @@
    * @return {boolean}
    * @private
    */
-  shouldShowMenu_: function(source) {
-    return !(this.isExceptionControlled_(source) || this.allSites);
+  isActionMenuHidden_: function(source) {
+    return this.isExceptionControlled_(source) || this.allSites;
   },
 
   /**
diff --git a/chrome/browser/safe_browsing/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection_service.cc
index 1aa5e7f..bd74a18 100644
--- a/chrome/browser/safe_browsing/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection_service.cc
@@ -38,6 +38,7 @@
 #include "chrome/browser/safe_browsing/download_feedback_service.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/sandboxed_zip_analyzer.h"
+#include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/common/pref_names.h"
@@ -73,6 +74,7 @@
 #endif
 
 using content::BrowserThread;
+namespace safe_browsing {
 
 namespace {
 
@@ -80,6 +82,9 @@
 // We sample 1% of whitelisted downloads to still send out download pings.
 const double kWhitelistDownloadSampleRate = 0.01;
 
+// The number of user gestures we trace back for download attribution.
+const int kDownloadAttributionUserGestureLimit = 2;
+
 const char kDownloadExtensionUmaName[] = "SBClientDownload.DownloadExtensions";
 const char kUnsupportedSchemeUmaPrefix[] = "SBClientDownload.UnsupportedScheme";
 
@@ -97,8 +102,6 @@
 
 }  // namespace
 
-namespace safe_browsing {
-
 const char DownloadProtectionService::kDownloadRequestUrl[] =
     "https://sb-ssl.google.com/safebrowsing/clientreport/download";
 
@@ -1002,6 +1005,12 @@
         item_->GetTargetFilePath().BaseName().AsUTF8Unsafe());
     }
     request.set_download_type(type_);
+
+    service_->AddReferrerChainToClientDownloadRequest(
+      item_->GetURL(),
+      item_->GetWebContents(),
+      &request);
+
     if (archive_is_valid_ != ArchiveValid::UNSET)
       request.set_archive_valid(archive_is_valid_ == ArchiveValid::VALID);
     request.mutable_signature()->CopyFrom(signature_info_);
@@ -1349,6 +1358,10 @@
       *(request.add_alternate_extensions()) =
           base::FilePath(default_file_path_.FinalExtension()).AsUTF8Unsafe();
     }
+    service_->AddReferrerChainToClientDownloadRequest(
+      requestor_url_,
+      nullptr,
+      &request);
 
     if (!request.SerializeToString(&client_download_request_data_)) {
       // More of an internal error than anything else. Note that the UNKNOWN
@@ -1516,6 +1529,7 @@
   if (sb_service) {
     ui_manager_ = sb_service->ui_manager();
     database_manager_ = sb_service->database_manager();
+    navigation_observer_manager_ = sb_service->navigation_observer_manager();
     ParseManualBlacklistFlag();
   }
 }
@@ -1779,4 +1793,35 @@
   return url;
 }
 
+void DownloadProtectionService::AddReferrerChainToClientDownloadRequest(
+    const GURL& download_url,
+    content::WebContents* web_contents,
+    ClientDownloadRequest* out_request) {
+  if (!base::FeatureList::IsEnabled(
+      SafeBrowsingNavigationObserverManager::kDownloadAttribution) ||
+      !navigation_observer_manager_) {
+    return;
+  }
+
+  int download_tab_id = SessionTabHelper::IdForTab(web_contents);
+  UMA_HISTOGRAM_BOOLEAN(
+      "SafeBrowsing.ReferrerHasInvalidTabID.DownloadAttribution",
+      download_tab_id == -1);
+  std::vector<ReferrerChainEntry> attribution_chain;
+  SafeBrowsingNavigationObserverManager::AttributionResult result =
+      navigation_observer_manager_->IdentifyReferrerChain(
+          download_url,
+          download_tab_id,
+          kDownloadAttributionUserGestureLimit,
+          &attribution_chain);
+  UMA_HISTOGRAM_COUNTS_100(
+      "SafeBrowsing.ReferrerURLChainSize.DownloadAttribution",
+      attribution_chain.size());
+  UMA_HISTOGRAM_ENUMERATION(
+      "SafeBrowsing.ReferrerAttributionResult.DownloadAttribution", result,
+      SafeBrowsingNavigationObserverManager::ATTRIBUTION_FAILURE_TYPE_MAX);
+  for (auto entry : attribution_chain)
+    *out_request->add_referrer_chain() = std::move(entry);
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/download_protection_service.h b/chrome/browser/safe_browsing/download_protection_service.h
index fbe44d81..213e2a7 100644
--- a/chrome/browser/safe_browsing/download_protection_service.h
+++ b/chrome/browser/safe_browsing/download_protection_service.h
@@ -23,6 +23,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/supports_user_data.h"
+#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
 #include "chrome/browser/safe_browsing/ui_manager.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "net/url_request/url_request_context_getter.h"
@@ -287,9 +288,19 @@
   // Returns the URL that will be used for download requests.
   static GURL GetDownloadRequestUrl();
 
+  // If kDownloadAttribution feature is enabled, identify and add referrer chain
+  // info of a download to ClientDownloadRequest proto. This function also
+  // records UMA stats of download attribution result.
+  void AddReferrerChainToClientDownloadRequest(
+    const GURL& download_url,
+    content::WebContents* web_contents,
+    ClientDownloadRequest* out_request);
+
   // These pointers may be NULL if SafeBrowsing is disabled.
   scoped_refptr<SafeBrowsingUIManager> ui_manager_;
   scoped_refptr<SafeBrowsingDatabaseManager> database_manager_;
+  scoped_refptr<SafeBrowsingNavigationObserverManager>
+  navigation_observer_manager_;
 
   // The context we use to issue network requests.
   scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
diff --git a/chrome/browser/safe_browsing/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection_service_unittest.cc
index 1b44858b..20e6527 100644
--- a/chrome/browser/safe_browsing/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection_service_unittest.cc
@@ -431,6 +431,12 @@
     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, GetFullPath()).WillRepeatedly(ReturnRef(tmp_path_));
     EXPECT_CALL(*item, GetTargetFilePath())
         .WillRepeatedly(ReturnRef(final_path_));
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
index 3efb350..7e0b419 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
@@ -6,7 +6,10 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/time/time.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
+#include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/sessions/session_tab_helper.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
@@ -74,9 +77,16 @@
     content::WebContents* web_contents) {
   if (FromWebContents(web_contents))
     return;
-  // TODO(jialiul): This method will be called by TabHelpers::AttachTabHelpers.
-  // Complete this method when the entire class is ready.
-  NOTIMPLEMENTED();
+
+  if (safe_browsing::SafeBrowsingNavigationObserverManager::IsEnabledAndReady(
+        Profile::FromBrowserContext(web_contents->GetBrowserContext()))) {
+    web_contents->SetUserData(
+        kWebContentsUserDataKey,
+        new SafeBrowsingNavigationObserver(
+            web_contents,
+            g_browser_process->safe_browsing_service()
+                ->navigation_observer_manager()));
+  }
 }
 
 // static
@@ -115,11 +125,13 @@
          !SafeBrowsingNavigationObserverManager::IsUserGestureExpired(
              last_user_gesture_timestamp_)) ||
         !navigation_handle->IsRendererInitiated()) {
-      nav_event.is_user_initiated = has_user_gesture_;
-      manager_->OnUserGestureConsumed(web_contents(),
-                                      last_user_gesture_timestamp_);
+      nav_event.is_user_initiated = true;
+      if (has_user_gesture_) {
+        manager_->OnUserGestureConsumed(web_contents(),
+                                        last_user_gesture_timestamp_);
+        has_user_gesture_ = false;
+      }
     }
-    has_user_gesture_ = false;
   }
 
   // All the other fields are reconstructed based on current content of
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.h b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.h
index 85321e6..f08f42ef 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.h
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.h
@@ -64,7 +64,9 @@
 class SafeBrowsingNavigationObserver : public base::SupportsUserData::Data,
                                        public content::WebContentsObserver {
  public:
-  static void MaybeCreateForWebContents(content::WebContents* web_contents);
+  static void MaybeCreateForWebContents(
+      content::WebContents* web_contents);
+
   static SafeBrowsingNavigationObserver* FromWebContents(
       content::WebContents* web_contents);
 
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
index 7450d81..0e4c257 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
@@ -561,16 +561,10 @@
                            referrer_chain[2]);
 }
 
-// https://crbug.com/667784: The test is flaky on Linux.
-#if defined(OS_LINUX)
-#define MAYBE_SingleMetaRefreshRedirectTargetBlank DISABLED_SingleMetaRefreshRedirectTargetBlank
-#else
-#define MAYBE_SingleMetaRefreshRedirectTargetBlank SingleMetaRefreshRedirectTargetBlank
-#endif
 // Click on a link which navigates to a page then redirects to a download using
 // META HTTP-EQUIV="refresh". First navigation happens in target blank.
 IN_PROC_BROWSER_TEST_F(SBNavigationObserverBrowserTest,
-                       MAYBE_SingleMetaRefreshRedirectTargetBlank) {
+                       SingleMetaRefreshRedirectTargetBlank) {
   GURL initial_url = embedded_test_server()->GetURL(kSingleFrameTestURL);
   ClickTestLink("single_meta_refresh_redirect_target_blank", 2, initial_url);
   GURL redirect_url = embedded_test_server()->GetURL(kRedirectURL);
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
index 989f7d61..0d14f7827 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
@@ -9,10 +9,15 @@
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer.h"
+#include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/browser/tab_contents/retargeting_details.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/navigation_details.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
@@ -47,10 +52,13 @@
 // navigation related records that happened 2 minutes ago are considered as
 // expired. So we clean up these navigation footprints every 2 minutes.
 static const double kNavigationFootprintTTLInSecond = 120.0;
-// The number of user gestures we trace back for download attribution.
-static const int kDownloadAttributionUserGestureLimit = 2;
 
 // static
+const base::Feature
+SafeBrowsingNavigationObserverManager::kDownloadAttribution {
+    "DownloadAttribution", base::FEATURE_DISABLED_BY_DEFAULT
+};
+// static
 bool SafeBrowsingNavigationObserverManager::IsUserGestureExpired(
     const base::Time& timestamp) {
   return IsEventExpired(timestamp, kUserGestureTTLInSecond);
@@ -66,12 +74,23 @@
   return url;
 }
 
+// static
+bool SafeBrowsingNavigationObserverManager::IsEnabledAndReady(
+    Profile* profile) {
+  return base::FeatureList::IsEnabled(
+      SafeBrowsingNavigationObserverManager::kDownloadAttribution) &&
+      profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled) &&
+      g_browser_process->safe_browsing_service() &&
+      g_browser_process->safe_browsing_service()->navigation_observer_manager();
+}
+
 SafeBrowsingNavigationObserverManager::SafeBrowsingNavigationObserverManager() {
   registrar_.Add(this, chrome::NOTIFICATION_RETARGETING,
                  content::NotificationService::AllSources());
 
-  // TODO(jialiul): call ScheduleNextCleanUpAfterInterval() when this class is
-  // ready to be hooked into SafeBrowsingService.
+  // Schedule clean up in 2 minutes.
+  ScheduleNextCleanUpAfterInterval(
+      base::TimeDelta::FromSecondsD(kNavigationFootprintTTLInSecond));
 }
 
 void SafeBrowsingNavigationObserverManager::RecordNavigationEvent(
@@ -208,29 +227,6 @@
   return result;
 }
 
-void SafeBrowsingNavigationObserverManager::
-    AddReferrerChainToClientDownloadRequest(
-        const GURL& download_url,
-        content::WebContents* source_contents,
-        ClientDownloadRequest* out_request) {
-  int download_tab_id = SessionTabHelper::IdForTab(source_contents);
-  UMA_HISTOGRAM_BOOLEAN(
-      "SafeBrowsing.ReferrerHasInvalidTabID.DownloadAttribution",
-      download_tab_id == -1);
-  std::vector<ReferrerChainEntry> attribution_chain;
-  AttributionResult result = IdentifyReferrerChain(
-      download_url, download_tab_id, kDownloadAttributionUserGestureLimit,
-      &attribution_chain);
-  UMA_HISTOGRAM_COUNTS_100(
-      "SafeBrowsing.ReferrerURLChainSize.DownloadAttribution",
-      attribution_chain.size());
-  UMA_HISTOGRAM_ENUMERATION(
-      "SafeBrowsing.ReferrerAttributionResult.DownloadAttribution", result,
-      SafeBrowsingNavigationObserverManager::ATTRIBUTION_FAILURE_TYPE_MAX);
-  for (auto entry : attribution_chain)
-    *out_request->add_referrer_chain() = entry;
-}
-
 SafeBrowsingNavigationObserverManager::
     ~SafeBrowsingNavigationObserverManager() {}
 
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
index 7a47da8..9bfcfa1 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
@@ -5,12 +5,15 @@
 #ifndef CHROME_BROWSER_SAFE_BROWSING_SAFE_BROWSING_NAVIGATION_OBSERVER_MANAGER_H_
 #define CHROME_BROWSER_SAFE_BROWSING_SAFE_BROWSING_NAVIGATION_OBSERVER_MANAGER_H_
 
+#include "base/feature_list.h"
 #include "chrome/common/safe_browsing/csd.pb.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "url/gurl.h"
 
+class Profile;
+
 namespace safe_browsing {
 
 class SafeBrowsingNavigationObserver;
@@ -28,6 +31,8 @@
     : public content::NotificationObserver,
       public base::RefCountedThreadSafe<SafeBrowsingNavigationObserverManager> {
  public:
+  static const base::Feature kDownloadAttribution;
+
   // For UMA histogram counting. Do NOT change order.
   enum AttributionResult {
     SUCCESS = 1,                   // Identified referrer chain is not empty.
@@ -43,12 +48,18 @@
   // Helper function to check if user gesture is older than
   // kUserGestureTTLInSecond.
   static bool IsUserGestureExpired(const base::Time& timestamp);
+
   // Helper function to strip empty ref fragment from a URL. Many pages
   // end up with a "#" at the end of their URLs due to navigation triggered by
   // href="#" and javascript onclick function. We don't want to have separate
   // entries for these cases in the maps.
   static GURL ClearEmptyRef(const GURL& url);
 
+  // Checks if we should enable observing navigations for safe browsing purpose.
+  // Return true if the safe browsing service and the download attribution
+  // feature are both enabled, and safe browsing service is initialized.
+  static bool IsEnabledAndReady(Profile* profile);
+
   SafeBrowsingNavigationObserverManager();
 
   // Add |nav_event| to |navigation_map_| based on |nav_event_key|. Object
@@ -80,15 +91,6 @@
       int user_gesture_count_limit,
       std::vector<ReferrerChainEntry>* out_referrer_chain);
 
-  // Identify and add referrer chain info of a download to ClientDownloadRequest
-  // proto. This function also record UMA stats of download attribution result.
-  // TODO(jialiul): This function will be moved to DownloadProtectionService
-  // class shortly.
-  void AddReferrerChainToClientDownloadRequest(
-      const GURL& download_url,
-      content::WebContents* source_contents,
-      ClientDownloadRequest* out_request);
-
  private:
   friend class base::RefCountedThreadSafe<
       SafeBrowsingNavigationObserverManager>;
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc
index d03c2e46..eafb8f1 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -28,6 +28,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/safe_browsing/ping_manager.h"
+#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
 #include "chrome/browser/safe_browsing/ui_manager.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
@@ -309,6 +310,11 @@
     database_manager_ = CreateDatabaseManager();
   }
 
+  if (base::FeatureList::IsEnabled(
+    SafeBrowsingNavigationObserverManager::kDownloadAttribution)) {
+    navigation_observer_manager_ = new SafeBrowsingNavigationObserverManager();
+  }
+
   services_delegate_->Initialize();
   services_delegate_->InitializeCsdService(url_request_context_getter_.get());
 
@@ -393,6 +399,11 @@
   return enabled_v4_only_ ? v4_local_database_manager() : database_manager_;
 }
 
+scoped_refptr<SafeBrowsingNavigationObserverManager>
+SafeBrowsingService::navigation_observer_manager() {
+  return navigation_observer_manager_;
+}
+
 SafeBrowsingProtocolManager* SafeBrowsingService::protocol_manager() const {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 #if defined(SAFE_BROWSING_DB_LOCAL)
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.h b/chrome/browser/safe_browsing/safe_browsing_service.h
index 27e15b0..7e7668e 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.h
+++ b/chrome/browser/safe_browsing/safe_browsing_service.h
@@ -49,6 +49,7 @@
 struct ResourceRequestInfo;
 struct SafeBrowsingProtocolConfig;
 class SafeBrowsingDatabaseManager;
+class SafeBrowsingNavigationObserverManager;
 class SafeBrowsingPingManager;
 class SafeBrowsingProtocolManager;
 class SafeBrowsingProtocolManagerDelegate;
@@ -129,6 +130,9 @@
   // the experiment settings.
   const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager() const;
 
+  scoped_refptr<SafeBrowsingNavigationObserverManager>
+  navigation_observer_manager();
+
   SafeBrowsingProtocolManager* protocol_manager() const;
 
   SafeBrowsingPingManager* ping_manager() const;
@@ -291,6 +295,10 @@
   // both UI and IO thread.
   scoped_refptr<SafeBrowsingDatabaseManager> database_manager_;
 
+  // The navigation observer manager handles download attribution.
+  scoped_refptr<SafeBrowsingNavigationObserverManager>
+  navigation_observer_manager_;
+
   DISALLOW_COPY_AND_ASSIGN(SafeBrowsingService);
 };
 
diff --git a/chrome/browser/search_engines/template_url_service_unittest.cc b/chrome/browser/search_engines/template_url_service_unittest.cc
index f872c83..ddc0638 100644
--- a/chrome/browser/search_engines/template_url_service_unittest.cc
+++ b/chrome/browser/search_engines/template_url_service_unittest.cc
@@ -1005,6 +1005,35 @@
   EXPECT_TRUE(model()->GetTemplateURLForHost("google.co.uk") == NULL);
   EXPECT_EQ("google.fr", t_url->url_ref().GetHost(search_terms_data()));
   EXPECT_EQ(ASCIIToUTF16("google.fr"), t_url->keyword());
+
+  // Now add an OSDD entry and then change the Google base URL such that the
+  // autogenerated Google search keyword would conflict.
+  TemplateURL* osdd = AddKeywordWithDate(
+      "osdd", "google.it", "http://google.it/search?q={searchTerms}",
+      std::string(), std::string(), std::string(), true, "UTF-8", Time(),
+      Time(), Time());
+  ASSERT_EQ(osdd,
+            model()->GetTemplateURLForKeyword(ASCIIToUTF16("google.it")));
+  EXPECT_EQ(ASCIIToUTF16("google.it"), osdd->keyword());
+  ASSERT_EQ(osdd, model()->GetTemplateURLForHost("google.it"));
+  EXPECT_EQ("google.it", osdd->url_ref().GetHost(search_terms_data()));
+  const std::string osdd_guid = osdd->sync_guid();
+  ASSERT_EQ(osdd, model()->GetTemplateURLForGUID(osdd_guid));
+  test_util()->SetGoogleBaseURL(GURL("http://google.it"));
+
+  // Verify that the osdd entry was removed, and the autogenerated keyword has
+  // changed.
+  EXPECT_FALSE(model()->GetTemplateURLForGUID(osdd_guid));
+  ASSERT_EQ(t_url,
+            model()->GetTemplateURLForKeyword(ASCIIToUTF16("google.it")));
+  EXPECT_EQ(ASCIIToUTF16("google.it"), t_url->keyword());
+  ASSERT_EQ(t_url, model()->GetTemplateURLForHost("google.it"));
+  EXPECT_EQ("google.it", t_url->url_ref().GetHost(search_terms_data()));
+  ASSERT_EQ(manual,
+            model()->GetTemplateURLForKeyword(ASCIIToUTF16("google.de")));
+  EXPECT_EQ(ASCIIToUTF16("google.de"), manual->keyword());
+  ASSERT_EQ(manual, model()->GetTemplateURLForHost("google.de"));
+  EXPECT_EQ("google.de", manual->url_ref().GetHost(search_terms_data()));
 }
 
 // Make sure TemplateURLService generates a KEYWORD_GENERATED visit for
diff --git a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos_unittest.cc b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos_unittest.cc
index 2464dfa..2532ed09 100644
--- a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos_unittest.cc
+++ b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos_unittest.cc
@@ -53,9 +53,12 @@
 
 namespace {
 
-const char kAAccountIdString[] = "{\"email\":\"A\",\"gaia_id\":\"\"}";
-const char kBAccountIdString[] = "{\"email\":\"B\",\"gaia_id\":\"\"}";
-const char kArrowBAccountIdString[] = "->{\"email\":\"B\",\"gaia_id\":\"\"}";
+const char kAAccountIdString[] =
+    "{\"account_type\":\"google\",\"email\":\"A\",\"gaia_id\":\"\"}";
+const char kBAccountIdString[] =
+    "{\"account_type\":\"google\",\"email\":\"B\",\"gaia_id\":\"\"}";
+const char kArrowBAccountIdString[] =
+    "->{\"account_type\":\"google\",\"email\":\"B\",\"gaia_id\":\"\"}";
 
 // TOOD(beng): This implementation seems only superficially different to the
 //             production impl. Evaluate whether or not we can just use that
diff --git a/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.mm b/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.mm
index d6efcae5..444110b2 100644
--- a/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.mm
+++ b/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.mm
@@ -472,12 +472,14 @@
                                    title:base::SysUTF8ToNSString(listItem.title)
                                     icon:image
                           referenceFrame:radioFrame];
+      [button setAutoresizingMask:NSViewMinYMargin];
       [[self bubble] addSubview:button];
       popupLinks_[button] = row++;
     } else {
       NSTextField* label =
           LabelWithFrame(base::SysUTF8ToNSString(listItem.title), frame);
       SetControlSize(label, NSSmallControlSize);
+      [label setAutoresizingMask:NSViewMinYMargin];
       [[self bubble] addSubview:label];
       row++;
     }
@@ -782,10 +784,14 @@
 - (void)awakeFromNib {
   [super awakeFromNib];
 
+  ContentSettingSimpleBubbleModel* simple_bubble =
+      contentSettingBubbleModel_->AsSimpleBubbleModel();
+
   [[self bubble] setArrowLocation:info_bubble::kTopRight];
 
   // Adapt window size to bottom buttons. Do this before all other layouting.
-  [self initManageDoneButtons];
+  if (simple_bubble && !simple_bubble->bubble_content().manage_text.empty())
+    [self initManageDoneButtons];
 
   [self initializeTitle];
   [self initializeMessage];
@@ -793,11 +799,11 @@
   // Note that the per-content-type methods and |initializeRadioGroup| below
   // must be kept in the correct order, as they make interdependent adjustments
   // of the bubble's height.
-  ContentSettingSimpleBubbleModel* simple_bubble =
-      contentSettingBubbleModel_->AsSimpleBubbleModel();
   if (simple_bubble &&
       simple_bubble->content_type() == CONTENT_SETTINGS_TYPE_PLUGINS) {
-    [self sizeToFitLoadButton];
+    if (!simple_bubble->bubble_content().custom_link.empty())
+      [self sizeToFitLoadButton];
+
     [self initializeBlockedPluginsList];
   }
 
@@ -814,6 +820,28 @@
       [self initializeGeoLists];
     if (type == CONTENT_SETTINGS_TYPE_MIDI_SYSEX)
       [self initializeMIDISysExLists];
+
+    // For plugins, many controls are now removed. Remove them after the item
+    // list has been placed to preserve the existing layout logic.
+    if (type == CONTENT_SETTINGS_TYPE_PLUGINS) {
+      // The radio group is no longer applicable to plugins.
+      int delta = NSHeight([allowBlockRadioGroup_ frame]);
+      [allowBlockRadioGroup_ removeFromSuperview];
+
+      // Remove the "Load All" button if the model specifes it as empty.
+      if (simple_bubble->bubble_content().custom_link.empty()) {
+        delta += NSHeight([loadButton_ frame]);
+        [loadButton_ removeFromSuperview];
+      }
+
+      // Remove the "Manage" button if the model specifies it as empty.
+      if (simple_bubble->bubble_content().manage_text.empty())
+        [manageButton_ removeFromSuperview];
+
+      NSRect frame = [[self window] frame];
+      frame.size.height -= delta;
+      [[self window] setFrame:frame display:NO];
+    }
   }
 
   if (contentSettingBubbleModel_->AsMediaStreamBubbleModel())
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 74e762c..0ef2975 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -77,6 +77,7 @@
 #else
 #include "chrome/browser/banners/app_banner_manager_desktop.h"
 #include "chrome/browser/plugins/plugin_observer.h"
+#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer.h"
 #include "chrome/browser/safe_browsing/safe_browsing_tab_observer.h"
 #include "chrome/browser/thumbnails/thumbnail_tab_helper.h"
 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
@@ -228,6 +229,8 @@
   PluginObserver::CreateForWebContents(web_contents);
   SadTabHelper::CreateForWebContents(web_contents);
   safe_browsing::SafeBrowsingTabObserver::CreateForWebContents(web_contents);
+  safe_browsing::SafeBrowsingNavigationObserver::MaybeCreateForWebContents(
+      web_contents);
   TabContentsSyncedTabDelegate::CreateForWebContents(web_contents);
   TabDialogs::CreateForWebContents(web_contents);
   ThumbnailTabHelper::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index 5660e1b..460bf6e 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -88,9 +88,9 @@
 
     // If there's a populated email, we must check first that this user is using
     // SAML in order to decide whether to show the interstitial page.
-    const user_manager::User* user =
-        user_manager::UserManager::Get()->FindUser(
-            user_manager::known_user::GetAccountId(email, std::string()));
+    const user_manager::User* user = user_manager::UserManager::Get()->FindUser(
+        user_manager::known_user::GetAccountId(email, std::string() /* id */,
+                                               AccountType::UNKNOWN));
 
     if (user && user->using_saml())
       return GAIA_SCREEN_MODE_SAML_INTERSTITIAL;
@@ -406,7 +406,7 @@
 
 void GaiaScreenHandler::HandleIdentifierEntered(const std::string& user_email) {
   if (!Delegate()->IsUserWhitelisted(user_manager::known_user::GetAccountId(
-          user_email, std::string() /* gaia_id */)))
+          user_email, std::string() /* id */, AccountType::UNKNOWN)))
     ShowWhitelistCheckFailedError();
 }
 
@@ -451,12 +451,13 @@
 
 AccountId GaiaScreenHandler::GetAccountId(
     const std::string& authenticated_email,
-    const std::string& gaia_id) const {
+    const std::string& id,
+    const AccountType& account_type) const {
   const std::string canonicalized_email =
       gaia::CanonicalizeEmail(gaia::SanitizeEmail(authenticated_email));
 
-  const AccountId account_id =
-      user_manager::known_user::GetAccountId(authenticated_email, gaia_id);
+  const AccountId account_id = user_manager::known_user::GetAccountId(
+      authenticated_email, id, account_type);
 
   if (account_id.GetUserEmail() != canonicalized_email) {
     LOG(WARNING) << "Existing user '" << account_id.GetUserEmail()
@@ -481,7 +482,7 @@
   const std::string sanitized_email = gaia::SanitizeEmail(email);
   Delegate()->SetDisplayEmail(sanitized_email);
 
-  UserContext user_context(GetAccountId(email, gaia_id));
+  UserContext user_context(GetAccountId(email, gaia_id, AccountType::GOOGLE));
   user_context.SetKey(Key(password));
   user_context.SetAuthCode(auth_code);
   user_context.SetAuthFlow(using_saml
@@ -566,7 +567,8 @@
   DCHECK(!gaia_id.empty());
   const std::string sanitized_email = gaia::SanitizeEmail(typed_email);
   Delegate()->SetDisplayEmail(sanitized_email);
-  UserContext user_context(GetAccountId(typed_email, gaia_id));
+  UserContext user_context(
+      GetAccountId(typed_email, gaia_id, AccountType::GOOGLE));
   user_context.SetKey(Key(password));
   user_context.SetAuthFlow(using_saml
                                ? UserContext::AUTH_FLOW_GAIA_WITH_SAML
@@ -705,7 +707,7 @@
     Delegate()->LoadSigninWallpaper();
   } else {
     Delegate()->LoadWallpaper(user_manager::known_user::GetAccountId(
-        populated_email_, std::string()));
+        populated_email_, std::string() /* id */, AccountType::UNKNOWN));
   }
 
   input_method::InputMethodManager* imm =
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h
index 21c5497..45d3fa2 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h
@@ -179,7 +179,8 @@
   // Returns user canonical e-mail. Finds already used account alias, if
   // user has already signed in.
   AccountId GetAccountId(const std::string& authenticated_email,
-                         const std::string& gaia_id) const;
+                         const std::string& id,
+                         const AccountType& account_type) const;
 
   bool offline_login_is_active() const { return offline_login_is_active_; }
   void set_offline_login_is_active(bool offline_login_is_active) {
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index c3067a60c..a0c72de2 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -1216,7 +1216,6 @@
   chromeos::DBusThreadManager::Get()
       ->GetUpstartClient()
       ->StartAuthPolicyService();
-  HandleToggleEnrollmentScreen();
 }
 
 void SigninScreenHandler::HandleToggleEnableDebuggingScreen() {
diff --git a/chrome/browser/ui/webui/options/chromeos/user_image_source.cc b/chrome/browser/ui/webui/options/chromeos/user_image_source.cc
index d302cbf..c5858ea 100644
--- a/chrome/browser/ui/webui/options/chromeos/user_image_source.cc
+++ b/chrome/browser/ui/webui/options/chromeos/user_image_source.cc
@@ -38,7 +38,7 @@
   if (!status) {
     LOG(WARNING) << "Failed to deserialize account_id.";
     account_id = user_manager::known_user::GetAccountId(
-        serialized_account_id, std::string() /* gaia_id */);
+        serialized_account_id, std::string() /* id */, AccountType::UNKNOWN);
   }
   *email = account_id.GetUserEmail();
 }
diff --git a/chrome/browser/ui/webui/snippets_internals_message_handler.cc b/chrome/browser/ui/webui/snippets_internals_message_handler.cc
index e1fd28ca..670b9fd92 100644
--- a/chrome/browser/ui/webui/snippets_internals_message_handler.cc
+++ b/chrome/browser/ui/webui/snippets_internals_message_handler.cc
@@ -28,6 +28,7 @@
 #include "components/ntp_snippets/category_info.h"
 #include "components/ntp_snippets/features.h"
 #include "components/ntp_snippets/pref_names.h"
+#include "components/ntp_snippets/remote/ntp_snippets_fetcher.h"
 #include "components/ntp_snippets/remote/remote_suggestions_provider.h"
 #include "components/ntp_snippets/switches.h"
 #include "components/prefs/pref_service.h"
@@ -38,6 +39,7 @@
 using ntp_snippets::CategoryInfo;
 using ntp_snippets::CategoryStatus;
 using ntp_snippets::KnownCategories;
+using ntp_snippets::RemoteSuggestionsProvider;
 using ntp_snippets::UserClassifier;
 
 namespace {
@@ -88,7 +90,8 @@
       dom_loaded_(false),
       content_suggestions_service_(content_suggestions_service),
       remote_suggestions_provider_(
-          content_suggestions_service_->ntp_snippets_service()),
+          content_suggestions_service_
+              ->remote_suggestions_provider_for_debugging()),
       pref_service_(pref_service),
       weak_ptr_factory_(this) {}
 
@@ -195,7 +198,7 @@
   if (!remote_suggestions_provider_)
     return;
 
-  remote_suggestions_provider_->FetchSnippetsForAllCategories();
+  remote_suggestions_provider_->ReloadSuggestions();
 }
 
 void SnippetsInternalsMessageHandler::HandleClearCachedSuggestions(
@@ -265,7 +268,7 @@
 void SnippetsInternalsMessageHandler::FetchRemoteSuggestionsInTheBackground(
     const base::ListValue* args) {
   DCHECK_EQ(0u, args->GetSize());
-  remote_suggestions_provider_->FetchSnippetsInTheBackground();
+  remote_suggestions_provider_->RefetchInTheBackground(nullptr);
 }
 
 void SnippetsInternalsMessageHandler::SendAllContent() {
@@ -295,18 +298,16 @@
   SendLastRemoteSuggestionsBackgroundFetchTime();
 
   if (remote_suggestions_provider_) {
+    const ntp_snippets::NTPSnippetsFetcher* fetcher =
+        remote_suggestions_provider_
+            ->snippets_fetcher_for_testing_and_debugging();
     // TODO(fhorschig): Read this string from variations directly.
-    SendString("switch-personalized",
-               remote_suggestions_provider_->snippets_fetcher()
-                   ->PersonalizationModeString());
+    SendString("switch-personalized", fetcher->PersonalizationModeString());
 
-    SendString(
-        "switch-fetch-url",
-        remote_suggestions_provider_->snippets_fetcher()->fetch_url().spec());
+    SendString("switch-fetch-url", fetcher->fetch_url().spec());
     web_ui()->CallJavascriptFunctionUnsafe(
         "chrome.SnippetsInternals.receiveJson",
-        base::StringValue(
-            remote_suggestions_provider_->snippets_fetcher()->last_json()));
+        base::StringValue(fetcher->last_json()));
   }
 
   SendContentSuggestions();
@@ -377,7 +378,9 @@
 
   if (remote_suggestions_provider_) {
     const std::string& status =
-        remote_suggestions_provider_->snippets_fetcher()->last_status();
+        remote_suggestions_provider_
+            ->snippets_fetcher_for_testing_and_debugging()
+            ->last_status();
     if (!status.empty())
       SendString("remote-status", "Finished: " + status);
   }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 5da09985..803afb9 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3240,6 +3240,7 @@
     "../browser/prefs/chrome_command_line_pref_store_unittest.cc",
     "../browser/prefs/chrome_pref_service_unittest.cc",
     "../browser/prefs/incognito_mode_prefs_unittest.cc",
+    "../browser/prefs/preferences_manager_unittest.cc",
     "../browser/prefs/profile_pref_store_manager_unittest.cc",
     "../browser/prefs/proxy_policy_unittest.cc",
     "../browser/prefs/session_startup_pref_unittest.cc",
diff --git a/chrome/test/data/webui/settings/site_list_tests.js b/chrome/test/data/webui/settings/site_list_tests.js
index 108830c..b43a0cc 100644
--- a/chrome/test/data/webui/settings/site_list_tests.js
+++ b/chrome/test/data/webui/settings/site_list_tests.js
@@ -709,6 +709,22 @@
             });
       });
 
+      test('All sites category no action menu', function() {
+        setUpCategory(settings.ALL_SITES, '', prefsVarious);
+        return browserProxy.whenCalled('getExceptionList').then(
+            function(contentType) {
+              // Use resolver to ensure that the list container is populated.
+              var resolver = new PromiseResolver();
+              testElement.async(resolver.resolve);
+              return resolver.promise.then(function() {
+                var item = testElement.$.listContainer.children[0];
+                var dots = item.querySelector('paper-icon-button');
+                assertTrue(!!dots);
+                assertTrue(dots.hidden);
+              });
+            });
+      });
+
       test('All sites category', function() {
         // Prefs: Multiple and overlapping sites.
         setUpCategory(settings.ALL_SITES, '', prefsVarious);
diff --git a/chromeos/cryptohome/cryptohome_parameters.cc b/chromeos/cryptohome/cryptohome_parameters.cc
index 9ce9c91..4f6d557 100644
--- a/chromeos/cryptohome/cryptohome_parameters.cc
+++ b/chromeos/cryptohome/cryptohome_parameters.cc
@@ -19,7 +19,7 @@
 
 const std::string GetCryptohomeId(const AccountId& account_id) {
   // Guest/kiosk/managed/public accounts have empty GaiaId. Default to email.
-  if (account_id.GetGaiaId().empty())
+  if (account_id.GetAccountType() == AccountType::UNKNOWN)
     return account_id.GetUserEmail();  // Migrated
 
   if (GetGaiaIdMigrationStatus(account_id))
@@ -56,7 +56,7 @@
   // A LOT of tests start with --login_user <user>, and not registing this user
   // before. So we might have "known_user" entry without gaia_id.
   for (const AccountId& known_id : known_account_ids) {
-    if (!known_id.GetGaiaId().empty() && known_id.GetAccountIdKey() == id_) {
+    if (known_id.HasAccountIdKey() && known_id.GetAccountIdKey() == id_) {
       return known_id;
     }
   }
@@ -67,8 +67,8 @@
     }
   }
 
-  return user_manager::known_user::GetAccountId(id_,
-                                                std::string() /* gaia_id */);
+  return user_manager::known_user::GetAccountId(id_, std::string() /* id */,
+                                                AccountType::UNKNOWN);
 }
 
 KeyDefinition::AuthorizationData::Secret::Secret() : encrypt(false),
diff --git a/chromeos/dbus/debug_daemon_client.cc b/chromeos/dbus/debug_daemon_client.cc
index eda3a10..868a60f2 100644
--- a/chromeos/dbus/debug_daemon_client.cc
+++ b/chromeos/dbus/debug_daemon_client.cc
@@ -22,9 +22,8 @@
 #include "base/message_loop/message_loop.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/strings/string_util.h"
-#include "base/task_runner_util.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "base/threading/worker_pool.h"
 #include "chromeos/dbus/pipe_reader.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
@@ -55,9 +54,9 @@
 class PipeReaderWrapper : public base::SupportsWeakPtr<PipeReaderWrapper> {
  public:
   explicit PipeReaderWrapper(const DebugDaemonClient::GetLogsCallback& callback)
-      : task_runner_(
-            base::WorkerPool::GetTaskRunner(true /** tasks_are_slow */)),
-        pipe_reader_(task_runner_,
+      : pipe_reader_(base::CreateTaskRunnerWithTraits(
+                         base::TaskTraits().MayBlock().WithShutdownBehavior(
+                             base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN)),
                      base::Bind(&PipeReaderWrapper::OnIOComplete, AsWeakPtr())),
         callback_(callback) {}
 
@@ -93,7 +92,6 @@
   void TerminateStream() { pipe_reader_.OnDataReady(-1); }
 
  private:
-  scoped_refptr<base::TaskRunner> task_runner_;
   PipeReaderForString pipe_reader_;
   DebugDaemonClient::GetLogsCallback callback_;
 
diff --git a/chromeos/login/auth/cryptohome_authenticator.cc b/chromeos/login/auth/cryptohome_authenticator.cc
index 9fc700a..389dd6c 100644
--- a/chromeos/login/auth/cryptohome_authenticator.cc
+++ b/chromeos/login/auth/cryptohome_authenticator.cc
@@ -220,12 +220,12 @@
   }
   const bool already_migrated = cryptohome::GetGaiaIdMigrationStatus(
       attempt->user_context.GetAccountId());
-  const bool has_gaia_id =
-      !attempt->user_context.GetAccountId().GetGaiaId().empty();
+  const bool has_account_key =
+      attempt->user_context.GetAccountId().HasAccountIdKey();
 
   bool need_migration = false;
   if (!create_if_nonexistent && !already_migrated) {
-    if (has_gaia_id) {
+    if (has_account_key) {
       need_migration = true;
     } else {
       LOG(WARNING) << "Account '"
@@ -248,7 +248,7 @@
                    create_if_nonexistent));
     return;
   }
-  if (!already_migrated && has_gaia_id) {
+  if (!already_migrated && has_account_key) {
     // Mark new users migrated.
     cryptohome::SetGaiaIdMigrationStatusDone(
         attempt->user_context.GetAccountId());
diff --git a/chromeos/tpm/tpm_token_info_getter_unittest.cc b/chromeos/tpm/tpm_token_info_getter_unittest.cc
index cff50eaf..6e6a9d9 100644
--- a/chromeos/tpm/tpm_token_info_getter_unittest.cc
+++ b/chromeos/tpm/tpm_token_info_getter_unittest.cc
@@ -259,7 +259,7 @@
 class UserTPMTokenInfoGetterTest : public testing::Test {
  public:
   UserTPMTokenInfoGetterTest()
-      : account_id_(AccountId::FromUserEmail("user")) {}
+      : account_id_(AccountId::FromUserEmail("user@gmail.com")) {}
   ~UserTPMTokenInfoGetterTest() override {}
 
   void SetUp() override {
diff --git a/components/discardable_memory/service/discardable_shared_memory_manager.cc b/components/discardable_memory/service/discardable_shared_memory_manager.cc
index 6f5bf1c..a974b2d 100644
--- a/components/discardable_memory/service/discardable_shared_memory_manager.cc
+++ b/components/discardable_memory/service/discardable_shared_memory_manager.cc
@@ -12,7 +12,6 @@
 #include "base/callback.h"
 #include "base/command_line.h"
 #include "base/debug/crash_logging.h"
-#include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/memory/discardable_memory.h"
 #include "base/memory/memory_coordinator_client_registry.h"
@@ -207,9 +206,6 @@
                   base::SysInfo::AmountOfPhysicalMemory() / 4);
 }
 
-base::LazyInstance<DiscardableSharedMemoryManager>
-    g_discardable_shared_memory_manager = LAZY_INSTANCE_INITIALIZER;
-
 const int kEnforceMemoryPolicyDelayMs = 1000;
 
 // Global atomic to generate unique discardable shared memory IDs.
@@ -250,19 +246,6 @@
       this);
 }
 
-// static
-DiscardableSharedMemoryManager*
-DiscardableSharedMemoryManager::CreateInstance() {
-  DCHECK(g_discardable_shared_memory_manager == nullptr);
-  return g_discardable_shared_memory_manager.Pointer();
-}
-
-// static
-DiscardableSharedMemoryManager* DiscardableSharedMemoryManager::GetInstance() {
-  DCHECK(!(g_discardable_shared_memory_manager == nullptr));
-  return g_discardable_shared_memory_manager.Pointer();
-}
-
 void DiscardableSharedMemoryManager::Bind(
     mojom::DiscardableSharedMemoryManagerRequest request) {
   mojo::MakeStrongBinding(
diff --git a/components/discardable_memory/service/discardable_shared_memory_manager.h b/components/discardable_memory/service/discardable_shared_memory_manager.h
index 7df57de..9491af4 100644
--- a/components/discardable_memory/service/discardable_shared_memory_manager.h
+++ b/components/discardable_memory/service/discardable_shared_memory_manager.h
@@ -44,12 +44,6 @@
   DiscardableSharedMemoryManager();
   ~DiscardableSharedMemoryManager() override;
 
-  // Create a sigleton instance.
-  static DiscardableSharedMemoryManager* CreateInstance();
-
-  // Returns a singleton instance.
-  static DiscardableSharedMemoryManager* GetInstance();
-
   // Bind the manager to a mojo interface request.
   void Bind(mojom::DiscardableSharedMemoryManagerRequest request);
 
diff --git a/components/history/core/browser/typed_url_syncable_service.cc b/components/history/core/browser/typed_url_syncable_service.cc
index 0397d59..deaf6b27 100644
--- a/components/history/core/browser/typed_url_syncable_service.cc
+++ b/components/history/core/browser/typed_url_syncable_service.cc
@@ -71,7 +71,7 @@
       num_db_errors_(0),
       history_backend_observer_(this) {
   DCHECK(history_backend_);
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(sequence_checker_.CalledOnValidSequence());
 }
 
 TypedUrlSyncableService::~TypedUrlSyncableService() {
@@ -82,7 +82,7 @@
     const syncer::SyncDataList& initial_sync_data,
     std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
     std::unique_ptr<syncer::SyncErrorFactory> error_handler) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(sequence_checker_.CalledOnValidSequence());
   DCHECK(!sync_processor_.get());
   DCHECK(sync_processor.get());
   DCHECK(error_handler.get());
@@ -211,7 +211,7 @@
 }
 
 void TypedUrlSyncableService::StopSyncing(syncer::ModelType type) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(sequence_checker_.CalledOnValidSequence());
   DCHECK_EQ(type, syncer::TYPED_URLS);
 
   // Clear cache of server state.
@@ -227,7 +227,7 @@
 
 syncer::SyncDataList TypedUrlSyncableService::GetAllSyncData(
     syncer::ModelType type) const {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(sequence_checker_.CalledOnValidSequence());
   syncer::SyncDataList list;
 
   // TODO(sync): Add implementation
@@ -238,7 +238,7 @@
 syncer::SyncError TypedUrlSyncableService::ProcessSyncChanges(
     const tracked_objects::Location& from_here,
     const syncer::SyncChangeList& change_list) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(sequence_checker_.CalledOnValidSequence());
 
   std::vector<GURL> pending_deleted_urls;
   history::URLRows new_synced_urls;
@@ -288,7 +288,7 @@
 void TypedUrlSyncableService::OnURLsModified(
     history::HistoryBackend* history_backend,
     const history::URLRows& changed_urls) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(sequence_checker_.CalledOnValidSequence());
 
   if (processing_syncer_changes_)
     return;  // These are changes originating from us, ignore.
@@ -318,7 +318,7 @@
     const history::URLRow& row,
     const history::RedirectList& redirects,
     base::Time visit_time) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(sequence_checker_.CalledOnValidSequence());
 
   if (processing_syncer_changes_)
     return;  // These are changes originating from us, ignore.
@@ -343,7 +343,7 @@
     bool expired,
     const history::URLRows& deleted_rows,
     const std::set<GURL>& favicon_urls) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(sequence_checker_.CalledOnValidSequence());
 
   if (processing_syncer_changes_)
     return;  // These are changes originating from us, ignore.
diff --git a/components/history/core/browser/typed_url_syncable_service.h b/components/history/core/browser/typed_url_syncable_service.h
index 313021de..838e83e 100644
--- a/components/history/core/browser/typed_url_syncable_service.h
+++ b/components/history/core/browser/typed_url_syncable_service.h
@@ -12,7 +12,7 @@
 
 #include "base/macros.h"
 #include "base/scoped_observer.h"
-#include "base/threading/thread_checker.h"
+#include "base/sequence_checker.h"
 #include "components/history/core/browser/history_backend_observer.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/sync/model/sync_change.h"
@@ -232,7 +232,7 @@
   int num_db_accesses_;
   int num_db_errors_;
 
-  base::ThreadChecker thread_checker_;
+  base::SequenceChecker sequence_checker_;
 
   ScopedObserver<history::HistoryBackend, history::HistoryBackendObserver>
       history_backend_observer_;
diff --git a/components/minidump_uploader/BUILD.gn b/components/minidump_uploader/BUILD.gn
index a73f168..f27baf0 100644
--- a/components/minidump_uploader/BUILD.gn
+++ b/components/minidump_uploader/BUILD.gn
@@ -25,6 +25,7 @@
     ":minidump_uploader_java",
     "//base:base_java",
     "//base:base_java_test_support",
+    "//third_party/junit",
   ]
   java_files = [
     "android/javatests/src/org/chromium/components/minidump_uploader/CrashFileManagerTest.java",
diff --git a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/CrashFileManager.java b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/CrashFileManager.java
index 10a07e5..f0013517 100644
--- a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/CrashFileManager.java
+++ b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/CrashFileManager.java
@@ -10,6 +10,9 @@
 import org.chromium.base.VisibleForTesting;
 
 import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -21,6 +24,7 @@
 import java.util.NoSuchElementException;
 import java.util.Scanner;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
@@ -62,6 +66,9 @@
 
     private static final String UPLOAD_ATTEMPT_DELIMITER = ".try";
 
+    // A delimiter between uid and the rest of a minidump filename. Only used for WebView minidumps.
+    private static final String UID_DELIMITER = "_";
+
     @VisibleForTesting
     protected static final String TMP_SUFFIX = ".tmp";
 
@@ -78,6 +85,18 @@
     // users to eventually reclaim filesystem storage space from obsolete crash reports.
     private static final int MAX_CRASH_REPORT_AGE_IN_DAYS = 30;
 
+    // The maximum number of non-uploaded crashes to copy to the crash reports directory. The
+    // difference between this value and MAX_CRASH_REPORTS_TO_KEEP is that TO_KEEP is only checked
+    // when we clean out the crash directory - the TO_UPLOAD value is checked every time we try to
+    // copy a minidump - to ensure we don't store too many minidumps before they are cleaned out
+    // after being uploaded.
+    @VisibleForTesting
+    static final int MAX_CRASH_REPORTS_TO_UPLOAD = MAX_CRASH_REPORTS_TO_KEEP * 2;
+    // Same as above except this value is enforced per UID, so that one single app can't hog all
+    // storage/uploading resources.
+    @VisibleForTesting
+    static final int MAX_CRASH_REPORTS_TO_UPLOAD_PER_UID = MAX_CRASH_REPORTS_TO_KEEP;
+
     /**
      * Comparator used for sorting files by modification date.
      * @return Comparator for prioritizing the more recently modified file
@@ -253,12 +272,43 @@
     }
 
     /**
+     * Create the crash directory for this file manager unless it exists already.
+     * @return true iff the crash directory exists when this method returns.
+     */
+    public boolean ensureCrashDirExists() {
+        File crashDir = getCrashDirectory();
+        if (crashDir.exists()) return true;
+        return crashDir.mkdir();
+    }
+
+    /**
      * Returns all minidump files that could still be uploaded, sorted by modification time stamp.
      * Forced uploads are not included. Only returns files that we have tried to upload less
      * than {@param maxTries} number of times.
      */
     public File[] getAllMinidumpFiles(int maxTries) {
-        return getFilesBelowMaxTries(listCrashFiles(MINIDUMP_PATTERN), maxTries);
+        return getFilesBelowMaxTries(getAllMinidumpFilesIncludingFailed(), maxTries);
+    }
+
+    /**
+     * Returns all minidump files sorted by modification time stamp.
+     * Forced uploads are not included.
+     */
+    private File[] getAllMinidumpFilesIncludingFailed() {
+        return listCrashFiles(MINIDUMP_PATTERN);
+    }
+
+    /**
+     * Returns all minidump files with the uid {@param uid} from {@param minidumpFiles}.
+     */
+    public static List<File> filterMinidumpFilesOnUid(File[] minidumpFiles, int uid) {
+        List<File> uidMinidumps = new ArrayList<>();
+        for (File minidump : minidumpFiles) {
+            if (belongsToUid(minidump, uid)) {
+                uidMinidumps.add(minidump);
+            }
+        }
+        return uidMinidumps;
     }
 
     public void cleanOutAllNonFreshMinidumpFiles() {
@@ -339,7 +389,7 @@
     }
 
     @VisibleForTesting
-    File[] getAllUploadedFiles() {
+    public File[] getAllUploadedFiles() {
         return listCrashFiles(UPLOADED_MINIDUMP_PATTERN);
     }
 
@@ -402,7 +452,135 @@
         return new File(getCrashDirectory(), CRASH_DUMP_LOGFILE);
     }
 
-    private File[] getAllTempFiles() {
+    @VisibleForTesting
+    File[] getAllTempFiles() {
         return listCrashFiles(TMP_PATTERN);
     }
+
+    /**
+     * Delete the oldest minidump if we have reached our threshold on the number of minidumps to
+     * store (either per-app, or globally).
+     * @param uid The uid of the app to check the minidump limit for.
+     */
+    private void enforceMinidumpStorageRestrictions(int uid) {
+        File[] allMinidumpFiles = getAllMinidumpFilesIncludingFailed();
+        List<File> minidumpFilesWithCurrentUid = filterMinidumpFilesOnUid(allMinidumpFiles, uid);
+
+        // If we have exceeded our cap per uid, delete the oldest minidump of the same uid
+        if (minidumpFilesWithCurrentUid.size() >= MAX_CRASH_REPORTS_TO_UPLOAD_PER_UID) {
+            // Minidumps are sorted from newest to oldest.
+            File oldestFile =
+                    minidumpFilesWithCurrentUid.get(minidumpFilesWithCurrentUid.size() - 1);
+            if (!oldestFile.delete()) {
+                // Note that we will still try to copy the new file if this deletion fails.
+                Log.w(TAG, "Couldn't delete old minidump " + oldestFile.getAbsolutePath());
+            }
+            return;
+        }
+
+        // If we have exceeded our minidump cap, delete the oldest minidump.
+        if (allMinidumpFiles.length >= MAX_CRASH_REPORTS_TO_UPLOAD) {
+            // Minidumps are sorted from newest to oldest.
+            File oldestFile = allMinidumpFiles[allMinidumpFiles.length - 1];
+            if (!oldestFile.delete()) {
+                // Note that we will still try to copy the new file if this deletion fails.
+                Log.w(TAG, "Couldn't delete old minidump " + oldestFile.getAbsolutePath());
+            }
+        }
+    }
+
+    /**
+     * Copy a minidump from the File Descriptor {@param fd}.
+     * Use {@param tmpDir} as an intermediate location to store temporary files.
+     * @return The new minidump file copied with the contents of the File Descriptor, or null if the
+     *         copying failed.
+     */
+    public File copyMinidumpFromFD(FileDescriptor fd, File tmpDir, int uid)
+            throws IOException {
+        File crashDirectory = getCrashDirectory();
+        if (!crashDirectory.isDirectory() && !crashDirectory.mkdir()) {
+            throw new RuntimeException("Couldn't create " + crashDirectory.getAbsolutePath());
+        }
+        if (!tmpDir.isDirectory() && !tmpDir.mkdir()) {
+            throw new RuntimeException("Couldn't create " + tmpDir.getAbsolutePath());
+        }
+        if (tmpDir.getCanonicalPath().equals(crashDirectory.getCanonicalPath())) {
+            throw new RuntimeException("The tmp-dir and the crash dir can't have the same paths.");
+        }
+
+        enforceMinidumpStorageRestrictions(uid);
+
+        // Make sure the temp file doesn't overwrite an existing file.
+        File tmpFile = createMinidumpTmpFile(tmpDir);
+        FileInputStream in = null;
+        FileOutputStream out = null;
+        // TODO(gsennton): ensure that the copied file is indeed a minidump.
+        try {
+            in = new FileInputStream(fd);
+            out = new FileOutputStream(tmpFile);
+            final int bufSize = 4096;
+            byte[] buf = new byte[bufSize];
+            final int maxSize = 1024 * 1024; // 1MB maximum size
+            int curCount = in.read(buf);
+            int totalCount = curCount;
+            while ((curCount != -1) && (totalCount < maxSize)) {
+                out.write(buf, 0, curCount);
+                curCount = in.read(buf);
+                totalCount += curCount;
+            }
+            if (curCount != -1) {
+                // We are trying to keep on reading beyond our maximum threshold (1MB) - bail!
+                Log.w(TAG, "Tried to copy a file of size > 1MB, deleting the file and bailing!");
+                if (!tmpFile.delete()) {
+                    Log.w(TAG, "Couldn't delete file " + tmpFile.getAbsolutePath());
+                }
+                return null;
+            }
+        } finally {
+            try {
+                if (out != null) out.close();
+            } catch (IOException e) {
+                Log.w(TAG, "Couldn't close minidump output stream ", e);
+            }
+            try {
+                if (in != null) in.close();
+            } catch (IOException e) {
+                Log.w(TAG, "Couldn't close minidump input stream ", e);
+            }
+
+        }
+        File minidumpFile = new File(crashDirectory, createUniqueMinidumpNameForUid(uid));
+        if (tmpFile.renameTo(minidumpFile)) {
+            return minidumpFile;
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether the {@param minidump} belongs to the uid {@param uid}.
+     */
+    private static boolean belongsToUid(File minidump, int uid) {
+        return minidump.getName().startsWith(uid + UID_DELIMITER);
+    }
+
+    /**
+     * Returns a unique minidump name based on {@param uid} to differentiate between minidumps from
+     * different packages.
+     * The 'uniqueness' of the file name lies in it being created from a UUID. A UUID is a
+     * Universally Unique ID - it is simply a 128-bit value that can be used to uniquely identify
+     * some entity. A uid, on the other hand, is a unique identifier for Android packages.
+     */
+    private static String createUniqueMinidumpNameForUid(int uid) {
+        return uid + UID_DELIMITER + UUID.randomUUID() + NOT_YET_UPLOADED_MINIDUMP_SUFFIX;
+    }
+
+    /**
+     * Create a temporary file to store a minidump in before renaming it with a real minidump name.
+     * @return a new temporary file with prefix {@param prefix} stored in the directory
+     * {@param directory}.
+     *
+     */
+    private static File createMinidumpTmpFile(File directory) throws IOException {
+        return File.createTempFile("webview_minidump", TMP_SUFFIX, directory);
+    }
 }
diff --git a/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/CrashFileManagerTest.java b/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/CrashFileManagerTest.java
index 4438af0..4839aa9 100644
--- a/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/CrashFileManagerTest.java
+++ b/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/CrashFileManagerTest.java
@@ -4,6 +4,9 @@
 
 package org.chromium.components.minidump_uploader;
 
+import static org.junit.Assert.assertArrayEquals;
+
+import android.os.ParcelFileDescriptor;
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -12,6 +15,7 @@
 import org.chromium.base.test.util.Feature;
 
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Date;
@@ -339,6 +343,192 @@
         assertTrue(new File(mCrashDir, "123_abc.skipped0").exists());
     }
 
+    @SmallTest
+    @Feature({"Android-AppBase"})
+    public void testFilterMinidumpFilesOnUid() {
+        assertArrayEquals(new File[] {new File("1_md.dmp")},
+                CrashFileManager.filterMinidumpFilesOnUid(
+                        new File[] {new File("1_md.dmp")}, 1).toArray());
+        assertArrayEquals(new File[0],
+                CrashFileManager.filterMinidumpFilesOnUid(
+                        new File[] {new File("1_md.dmp")}, 12).toArray());
+        assertArrayEquals(new File[0],
+                CrashFileManager.filterMinidumpFilesOnUid(
+                        new File[] {new File("12_md.dmp")}, 1).toArray());
+        assertArrayEquals(new File[] {new File("1000_md.dmp")},
+                CrashFileManager.filterMinidumpFilesOnUid(
+                        new File[] {new File("1000_md.dmp")}, 1000).toArray());
+        assertArrayEquals(new File[0], CrashFileManager.filterMinidumpFilesOnUid(
+                new File[] {new File("-1000_md.dmp")}, -10).toArray());
+        assertArrayEquals(new File[] {new File("-10_md.dmp")},
+                CrashFileManager.filterMinidumpFilesOnUid(
+                        new File[] {new File("-10_md.dmp")}, -10).toArray());
+        assertArrayEquals(new File[] {
+                new File("12_md.dmp"),
+                new File("12_.dmp"),
+                new File("12_minidump.dmp"),
+                new File("12_1_md.dmp")},
+                CrashFileManager.filterMinidumpFilesOnUid(new File[] {
+                        new File("12_md.dmp"),
+                        new File("100_.dmp"),
+                        new File("4000_.dmp"),
+                        new File("12_.dmp"),
+                        new File("12_minidump.dmp"),
+                        new File("23_.dmp"),
+                        new File("12-.dmp"),
+                        new File("12*.dmp"),
+                        new File("12<.dmp"),
+                        new File("12=.dmp"),
+                        new File("12+.dmp"),
+                        new File("12_1_md.dmp"),
+                        new File("1_12_md.dmp")},
+                        12 /* uid */).toArray());
+    }
+
+    /**
+     * Ensure we handle minidump copying correctly when we have reached our limit on the number of
+     * stored minidumps for a certain uid.
+     */
+    @SmallTest
+    @Feature({"Android-AppBase"})
+    public void testMinidumpStorageRestrictionsPerUid() throws IOException {
+        testMinidumpStorageRestrictions(true /* perUid */);
+    }
+
+    /*
+     * Ensure we handle minidump copying correctly when we have reached our limit on the number of
+     * stored minidumps.
+     */
+    @SmallTest
+    @Feature({"Android-AppBase"})
+    public void testMinidumpStorageRestrictionsGlobal() throws IOException {
+        testMinidumpStorageRestrictions(false /* perUid */);
+    }
+
+    private static void deleteFilesInDirIfExists(File directory) {
+        if (directory.isDirectory()) {
+            File[] files = directory.listFiles();
+            if (files != null) {
+                for (File file : files) {
+                    if (!file.delete()) {
+                        throw new RuntimeException(
+                                "Couldn't delete file " + file.getAbsolutePath());
+                    }
+                }
+            }
+        }
+    }
+
+    private void testMinidumpStorageRestrictions(boolean perUid) throws IOException {
+        CrashFileManager fileManager =
+                new CrashFileManager(getInstrumentation().getTargetContext().getCacheDir());
+        // Delete existing minidumps to ensure they don't interfere with this test.
+        deleteFilesInDirIfExists(fileManager.getCrashDirectory());
+        assertEquals(0, fileManager.getAllMinidumpFiles(10000 /* maxTries */).length);
+        File tmpCopyDir = new File(getInstrumentation().getTargetContext().getCacheDir(), "tmpDir");
+
+        // Note that these minidump files are set up directly in the cache dir - not in the crash
+        // dir. This is to ensure the CrashFileManager doesn't see these minidumps without us first
+        // copying them.
+        File minidumpToCopy =
+                new File(getInstrumentation().getTargetContext().getCacheDir(), "toCopy.dmp");
+        setUpMinidumpFile(minidumpToCopy, "BOUNDARY");
+        // Ensure we didn't add any new minidumps to the crash directory.
+        assertEquals(0, fileManager.getAllMinidumpFiles(10000 /* maxTries */).length);
+
+        int minidumpLimit = perUid ? CrashFileManager.MAX_CRASH_REPORTS_TO_UPLOAD_PER_UID
+                                   : CrashFileManager.MAX_CRASH_REPORTS_TO_UPLOAD;
+        for (int n = 0; n < minidumpLimit; n++) {
+            ParcelFileDescriptor minidumpFd =
+                    ParcelFileDescriptor.open(minidumpToCopy, ParcelFileDescriptor.MODE_READ_ONLY);
+            try {
+                // If we are testing the app-throttling we want to use the same uid for each
+                // minidump, otherwise use a different one for each minidump.
+                int currentUid = perUid ? 1 : n;
+                assertNotNull(fileManager.copyMinidumpFromFD(
+                        minidumpFd.getFileDescriptor(), tmpCopyDir, currentUid));
+            } finally {
+                minidumpFd.close();
+            }
+        }
+
+        // Update time-stamps of copied files.
+        long initialTimestamp = new Date().getTime();
+        File[] minidumps = fileManager.getAllMinidumpFiles(10000 /* maxTries */);
+        for (int n = 0; n < minidumps.length; n++) {
+            if (!minidumps[n].setLastModified(initialTimestamp + n * 1000)) {
+                throw new RuntimeException(
+                        "Couldn't modify timestamp of " + minidumps[n].getAbsolutePath());
+            }
+        }
+
+        File[] allMinidumps = fileManager.getAllMinidumpFiles(10000 /* maxTries */);
+        assertEquals(minidumpLimit, allMinidumps.length);
+
+        File oldestMinidump = getOldestFile(allMinidumps);
+
+        // Now the crash directory is full - so copying a new minidump should cause the oldest
+        // existing minidump to be deleted.
+        ParcelFileDescriptor minidumpFd =
+                ParcelFileDescriptor.open(minidumpToCopy, ParcelFileDescriptor.MODE_READ_ONLY);
+        fileManager.copyMinidumpFromFD(minidumpFd.getFileDescriptor(), tmpCopyDir, 1 /* uid */);
+        assertEquals(minidumpLimit, fileManager.getAllMinidumpFiles(10000 /* maxTries */).length);
+        // Ensure we removed the oldest file.
+        assertFalse(oldestMinidump.exists());
+    }
+
+    /**
+     * Returns the oldest file in the set of files {@param files}.
+     */
+    private static File getOldestFile(File[] files) {
+        File oldestFile = null;
+        for (File file : files) {
+            if (oldestFile == null || oldestFile.lastModified() > file.lastModified()) {
+                oldestFile = file;
+            }
+        }
+        return oldestFile;
+    }
+
+    /**
+     * Ensure that we won't copy minidumps that are too large.
+     */
+    @MediumTest
+    @Feature({"Android-AppBase"})
+    public void testCantCopyLargeFile() throws IOException {
+        CrashFileManager fileManager =
+                new CrashFileManager(getInstrumentation().getTargetContext().getCacheDir());
+        // Delete existing minidumps to ensure they don't interfere with this test.
+        deleteFilesInDirIfExists(fileManager.getCrashDirectory());
+        assertEquals(0, fileManager.getAllMinidumpFiles(10000 /* maxTries */).length);
+        File tmpCopyDir = new File(getInstrumentation().getTargetContext().getCacheDir(), "tmpDir");
+
+        // Note that these minidump files are set up directly in the cache dir - not in the crash
+        // dir. This is to ensure the CrashFileManager doesn't see these minidumps without us first
+        // copying them.
+        File minidumpToCopy =
+                new File(getInstrumentation().getTargetContext().getCacheDir(), "toCopy.dmp");
+        setUpMinidumpFile(minidumpToCopy, "BOUNDARY");
+        // Write ~1MB data into the minidump file.
+        final int kilo = 1024;
+        byte[] kiloByteArray = new byte[kilo];
+        FileOutputStream minidumpOutputStream =
+                new FileOutputStream(minidumpToCopy, true /* append */);
+        try {
+            for (int n = 0; n < kilo; n++) {
+                minidumpOutputStream.write(kiloByteArray);
+            }
+        } finally {
+            minidumpOutputStream.close();
+        }
+        ParcelFileDescriptor minidumpFd =
+                ParcelFileDescriptor.open(minidumpToCopy, ParcelFileDescriptor.MODE_READ_ONLY);
+        assertNull(fileManager.copyMinidumpFromFD(
+                minidumpFd.getFileDescriptor(), tmpCopyDir, 0 /* uid */));
+        assertEquals(0, tmpCopyDir.listFiles().length);
+        assertEquals(0, fileManager.getAllMinidumpFiles(10000 /* maxTries */).length);
+    }
+
     @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
     @SmallTest
     @Feature({"Android-AppBase"})
diff --git a/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/CrashTestCase.java b/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/CrashTestCase.java
index 77b3841..78c8b9d5 100644
--- a/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/CrashTestCase.java
+++ b/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/CrashTestCase.java
@@ -105,7 +105,7 @@
         protected boolean mIsNetworkAvailable;
         protected boolean mIsEnabledForTests;
 
-        MockCrashReportingPermissionManager() {}
+        public MockCrashReportingPermissionManager() {}
 
         @Override
         public boolean isClientInMetricsSample() {
diff --git a/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java b/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java
index b892cb8..d485037 100644
--- a/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java
+++ b/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java
@@ -92,7 +92,11 @@
         }
     }
 
-    private static class TestHttpURLConnectionFactory implements HttpURLConnectionFactory {
+    /**
+     * A HttpURLConnectionFactory that performs some basic checks to ensure we are uploading
+     * minidumps correctly.
+     */
+    public static class TestHttpURLConnectionFactory implements HttpURLConnectionFactory {
         @Override
         public HttpURLConnection createHttpURLConnection(String url) {
             try {
diff --git a/components/ntp_snippets/BUILD.gn b/components/ntp_snippets/BUILD.gn
index b55ba7b4..c3477b30 100644
--- a/components/ntp_snippets/BUILD.gn
+++ b/components/ntp_snippets/BUILD.gn
@@ -53,15 +53,20 @@
     "remote/ntp_snippets_json_request.h",
     "remote/ntp_snippets_request_params.cc",
     "remote/ntp_snippets_request_params.h",
-    "remote/ntp_snippets_scheduler.h",
+    "remote/persistent_scheduler.h",
     "remote/remote_suggestions_database.cc",
     "remote/remote_suggestions_database.h",
     "remote/remote_suggestions_provider.cc",
     "remote/remote_suggestions_provider.h",
+    "remote/remote_suggestions_provider_impl.cc",
+    "remote/remote_suggestions_provider_impl.h",
+    "remote/remote_suggestions_scheduler.h",
     "remote/remote_suggestions_status_service.cc",
     "remote/remote_suggestions_status_service.h",
     "remote/request_throttler.cc",
     "remote/request_throttler.h",
+    "remote/scheduling_remote_suggestions_provider.cc",
+    "remote/scheduling_remote_suggestions_provider.h",
     "sessions/foreign_sessions_suggestions_provider.cc",
     "sessions/foreign_sessions_suggestions_provider.h",
     "sessions/tab_delegate_sync_adapter.cc",
@@ -130,9 +135,10 @@
     "remote/ntp_snippets_fetcher_unittest.cc",
     "remote/ntp_snippets_json_request_unittest.cc",
     "remote/remote_suggestions_database_unittest.cc",
-    "remote/remote_suggestions_provider_unittest.cc",
+    "remote/remote_suggestions_provider_impl_unittest.cc",
     "remote/remote_suggestions_status_service_unittest.cc",
     "remote/request_throttler_unittest.cc",
+    "remote/scheduling_remote_suggestions_provider_unittest.cc",
     "remote/test_utils.cc",
     "remote/test_utils.h",
     "sessions/foreign_sessions_suggestions_provider_unittest.cc",
diff --git a/components/ntp_snippets/content_suggestions_provider.h b/components/ntp_snippets/content_suggestions_provider.h
index bdf1622..dea83e7 100644
--- a/components/ntp_snippets/content_suggestions_provider.h
+++ b/components/ntp_snippets/content_suggestions_provider.h
@@ -104,6 +104,12 @@
                      const std::set<std::string>& known_suggestion_ids,
                      const FetchDoneCallback& callback) = 0;
 
+  // Reloads suggestions from all categories. If the suggestions change, the
+  // observer must be notified via OnNewSuggestions();
+  // TODO(jkcal): make pure virtual (involves touching all providers) or remove
+  // by resolving the pull/push dichotomy.
+  virtual void ReloadSuggestions() {}
+
   // Removes history from the specified time range where the URL matches the
   // |filter|. The data removed depends on the provider. Note that the
   // data outside the time range may be deleted, for example suggestions, which
diff --git a/components/ntp_snippets/content_suggestions_service.cc b/components/ntp_snippets/content_suggestions_service.cc
index fbfa2671..43dddc1 100644
--- a/components/ntp_snippets/content_suggestions_service.cc
+++ b/components/ntp_snippets/content_suggestions_service.cc
@@ -30,7 +30,8 @@
     : state_(state),
       signin_observer_(this),
       history_service_observer_(this),
-      ntp_snippets_service_(nullptr),
+      remote_suggestions_provider_(nullptr),
+      remote_suggestions_scheduler_(nullptr),
       pref_service_(pref_service),
       user_classifier_(pref_service),
       category_ranker_(std::move(category_ranker)) {
@@ -49,7 +50,8 @@
 ContentSuggestionsService::~ContentSuggestionsService() = default;
 
 void ContentSuggestionsService::Shutdown() {
-  ntp_snippets_service_ = nullptr;
+  remote_suggestions_provider_ = nullptr;
+  remote_suggestions_scheduler_ = nullptr;
   suggestions_by_category_.clear();
   providers_by_category_.clear();
   categories_.clear();
@@ -234,6 +236,12 @@
   providers_it->second->Fetch(category, known_suggestion_ids, callback);
 }
 
+void ContentSuggestionsService::ReloadSuggestions() {
+  for (const auto& provider : providers_) {
+    provider->ReloadSuggestions();
+  }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Private methods
 
diff --git a/components/ntp_snippets/content_suggestions_service.h b/components/ntp_snippets/content_suggestions_service.h
index cccb791..3896eda4 100644
--- a/components/ntp_snippets/content_suggestions_service.h
+++ b/components/ntp_snippets/content_suggestions_service.h
@@ -33,6 +33,7 @@
 namespace ntp_snippets {
 
 class RemoteSuggestionsProvider;
+class RemoteSuggestionsScheduler;
 
 // Retrieves suggestions from a number of ContentSuggestionsProviders and serves
 // them grouped into categories. There can be at most one provider per category.
@@ -143,10 +144,21 @@
   // Fetches additional contents for the given |category|. If the fetch was
   // completed, the given |callback| is called with the updated content.
   // This includes new and old data.
+  // TODO(jkrcal): Consider either renaming this to FetchMore or unify the ways
+  // to get suggestions to just this async Fetch() API.
   void Fetch(const Category& category,
              const std::set<std::string>& known_suggestion_ids,
              const FetchDoneCallback& callback);
 
+  // Reloads suggestions from all categories, from all providers. If a provider
+  // naturally has some ability to generate fresh suggestions, it may provide a
+  // completely new set of suggestions. If the provider has no ability to
+  // generate fresh suggestions on demand, it may only fill in any vacant space
+  // by suggestions that were previously not included due to space limits (there
+  // may be vacant space because of the user dismissing suggestions in the
+  // meantime).
+  void ReloadSuggestions();
+
   // Observer accessors.
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
@@ -194,15 +206,29 @@
   // supports it).
   void ClearDismissedSuggestionsForDebugging(Category category);
 
-  // The reference to the RemoteSuggestionsProvider provider should only be set
-  // by the factory and only be used for scheduling, periodic fetching and
-  // debugging.
-  RemoteSuggestionsProvider* ntp_snippets_service() {
-    return ntp_snippets_service_;
+  // The reference to the RemoteSuggestionsProvider provider should
+  // only be set by the factory and only used for debugging.
+  // TODO(jkrcal) The way we deal with the circular dependency feels wrong.
+  // Consider swapping the dependencies: first constructing all providers, then
+  // constructing the service (passing the remote provider as arg), finally
+  // registering the service as an observer of all providers?
+  void set_remote_suggestions_provider(
+      RemoteSuggestionsProvider* remote_suggestions_provider) {
+    remote_suggestions_provider_ = remote_suggestions_provider;
   }
-  void set_ntp_snippets_service(
-      RemoteSuggestionsProvider* ntp_snippets_service) {
-    ntp_snippets_service_ = ntp_snippets_service;
+  RemoteSuggestionsProvider* remote_suggestions_provider_for_debugging() {
+    return remote_suggestions_provider_;
+  }
+
+  // The reference to RemoteSuggestionsScheduler should only be set by the
+  // factory. The interface is suited for informing about external events that
+  // have influence on scheduling remote fetches.
+  void set_remote_suggestions_scheduler(
+      ntp_snippets::RemoteSuggestionsScheduler* remote_suggestions_scheduler) {
+    remote_suggestions_scheduler_ = remote_suggestions_scheduler;
+  }
+  RemoteSuggestionsScheduler* remote_suggestions_scheduler() {
+    return remote_suggestions_scheduler_;
   }
 
   UserClassifier* user_classifier() { return &user_classifier_; }
@@ -308,11 +334,14 @@
 
   const std::vector<ContentSuggestion> no_suggestions_;
 
-  // Keep a direct reference to this special provider to redirect scheduling,
-  // background fetching and debugging calls to it. If the
-  // RemoteSuggestionsProvider is loaded, it is also present in |providers_|,
-  // otherwise this is a nullptr.
-  RemoteSuggestionsProvider* ntp_snippets_service_;
+  // Keep a direct reference to this special provider to redirect debugging
+  // calls to it. If the RemoteSuggestionsProvider is loaded, it is also present
+  // in |providers_|, otherwise this is a nullptr.
+  RemoteSuggestionsProvider* remote_suggestions_provider_;
+
+  // Interface for informing about external events that have influence on
+  // scheduling remote fetches. Not owned.
+  RemoteSuggestionsScheduler* remote_suggestions_scheduler_;
 
   PrefService* pref_service_;
 
diff --git a/components/ntp_snippets/remote/ntp_snippets_scheduler.h b/components/ntp_snippets/remote/ntp_snippets_scheduler.h
deleted file mode 100644
index 7a4fc1ef..0000000
--- a/components/ntp_snippets/remote/ntp_snippets_scheduler.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_SCHEDULER_H_
-#define COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_SCHEDULER_H_
-
-#include "base/macros.h"
-#include "base/time/time.h"
-
-namespace ntp_snippets {
-
-// Interface to schedule the periodic fetching of snippets.
-class NTPSnippetsScheduler {
- public:
-  // Schedule periodic fetching of snippets, with different periods depending on
-  // network state. The concrete implementation should call
-  // NTPSnippetsService::FetchSnippets once per period.
-  // Any of the periods can be zero to indicate that the corresponding task
-  // should not be scheduled.
-  virtual bool Schedule(base::TimeDelta period_wifi,
-                        base::TimeDelta period_fallback) = 0;
-
-  // Cancel any scheduled tasks.
-  virtual bool Unschedule() = 0;
-
- protected:
-  NTPSnippetsScheduler() = default;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(NTPSnippetsScheduler);
-};
-
-}  // namespace ntp_snippets
-
-#endif  // COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_SCHEDULER_H_
diff --git a/components/ntp_snippets/remote/persistent_scheduler.h b/components/ntp_snippets/remote/persistent_scheduler.h
new file mode 100644
index 0000000..70a27e7
--- /dev/null
+++ b/components/ntp_snippets/remote/persistent_scheduler.h
@@ -0,0 +1,47 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_PERSISTENT_SCHEDULER_H_
+#define COMPONENTS_NTP_SNIPPETS_REMOTE_PERSISTENT_SCHEDULER_H_
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace ntp_snippets {
+
+// Interface to schedule persistent periodic fetches for remote suggestions, OS-
+// dependent. These persistent fetches must get triggered according to their
+// schedule independent of whether Chrome is running at that moment.
+//
+// Once per period, the concrete implementation should call
+// RemoteSuggestionsScheduler::OnFetchDue() where the scheduler object is
+// obtained from ContentSuggestionsService.
+//
+// The implementation may also call
+// RemoteSuggestionsScheduler::RescheduleFetching() when its own current
+// schedule got corrupted for whatever reason and needs to be applied again
+// (in turn, this will result in calling Schedule() on the implementation).
+class PersistentScheduler {
+ public:
+  // Schedule periodic fetching of remote suggestions, with different periods
+  // depending on network state. Any of the periods can be zero to indicate that
+  // the corresponding task should not be scheduled. Returns whether the
+  // scheduling was successful.
+  virtual bool Schedule(base::TimeDelta period_wifi,
+                        base::TimeDelta period_fallback) = 0;
+
+  // Cancel any scheduled tasks. Equivalent to Schedule(0, 0). Returns whether
+  // the scheduling was successful.
+  virtual bool Unschedule() = 0;
+
+ protected:
+  PersistentScheduler() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PersistentScheduler);
+};
+
+}  // namespace ntp_snippets
+
+#endif  // COMPONENTS_NTP_SNIPPETS_REMOTE_PERSISTENT_SCHEDULER_H_
diff --git a/components/ntp_snippets/remote/remote_suggestions_hard_scheduler.h b/components/ntp_snippets/remote/remote_suggestions_hard_scheduler.h
deleted file mode 100644
index 6487e2f8..0000000
--- a/components/ntp_snippets/remote/remote_suggestions_hard_scheduler.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_HARD_SCHEDULER_H_
-#define COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_HARD_SCHEDULER_H_
-
-#include "base/macros.h"
-#include "base/time/time.h"
-
-namespace ntp_snippets {
-
-// Interface to schedule "hard" periodic fetches of snippets. These "hard"
-// fetches must get triggered according to their schedule independent of whether
-// Chrome is running at that moment.
-class RemoteSuggestionsHardScheduler {
- public:
-  // Schedule periodic fetching of snippets, with different periods depending on
-  // network state. Once per period, the concrete implementation should call
-  // RemoteSuggestionsScheduler::Updater::HardUpdate() where the updater is
-  // obtained from ContentSuggestionsService. Any of the periods can be zero to
-  // indicate that the corresponding task should not be scheduled.
-  virtual bool Schedule(base::TimeDelta period_wifi,
-                        base::TimeDelta period_fallback) = 0;
-
-  // Cancel any scheduled tasks.
-  virtual bool Unschedule() = 0;
-
- protected:
-  RemoteSuggestionsHardScheduler() = default;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(RemoteSuggestionsHardScheduler);
-};
-
-}  // namespace ntp_snippets
-
-#endif  // COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_HARD_SCHEDULER_H_
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider.cc b/components/ntp_snippets/remote/remote_suggestions_provider.cc
index 3b64ebb..edfb5ea 100644
--- a/components/ntp_snippets/remote/remote_suggestions_provider.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_provider.cc
@@ -1,1352 +1,14 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright 2016 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "components/ntp_snippets/remote/remote_suggestions_provider.h"
 
-#include <algorithm>
-#include <iterator>
-#include <utility>
-
-#include "base/command_line.h"
-#include "base/feature_list.h"
-#include "base/location.h"
-#include "base/memory/ptr_util.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/metrics/sparse_histogram.h"
-#include "base/path_service.h"
-#include "base/stl_util.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/time/default_clock.h"
-#include "base/time/time.h"
-#include "base/values.h"
-#include "components/data_use_measurement/core/data_use_user_data.h"
-#include "components/history/core/browser/history_service.h"
-#include "components/image_fetcher/image_decoder.h"
-#include "components/image_fetcher/image_fetcher.h"
-#include "components/ntp_snippets/category_rankers/category_ranker.h"
-#include "components/ntp_snippets/features.h"
-#include "components/ntp_snippets/pref_names.h"
-#include "components/ntp_snippets/remote/ntp_snippets_request_params.h"
-#include "components/ntp_snippets/remote/remote_suggestions_database.h"
-#include "components/ntp_snippets/switches.h"
-#include "components/ntp_snippets/user_classifier.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/pref_service.h"
-#include "components/variations/variations_associated_data.h"
-#include "grit/components_strings.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/image/image.h"
-
 namespace ntp_snippets {
 
-namespace {
-
-// Number of snippets requested to the server. Consider replacing sparse UMA
-// histograms with COUNTS() if this number increases beyond 50.
-const int kMaxSnippetCount = 10;
-
-// Number of archived snippets we keep around in memory.
-const int kMaxArchivedSnippetCount = 200;
-
-// Default values for fetching intervals, fallback and wifi.
-const double kDefaultFetchingIntervalRareNtpUser[] = {48.0, 24.0};
-const double kDefaultFetchingIntervalActiveNtpUser[] = {24.0, 6.0};
-const double kDefaultFetchingIntervalActiveSuggestionsConsumer[] = {24.0, 6.0};
-
-// Variation parameters than can override the default fetching intervals.
-const char* kFetchingIntervalParamNameRareNtpUser[] = {
-    "fetching_interval_hours-fallback-rare_ntp_user",
-    "fetching_interval_hours-wifi-rare_ntp_user"};
-const char* kFetchingIntervalParamNameActiveNtpUser[] = {
-    "fetching_interval_hours-fallback-active_ntp_user",
-    "fetching_interval_hours-wifi-active_ntp_user"};
-const char* kFetchingIntervalParamNameActiveSuggestionsConsumer[] = {
-    "fetching_interval_hours-fallback-active_suggestions_consumer",
-    "fetching_interval_hours-wifi-active_suggestions_consumer"};
-
-// Keys for storing CategoryContent info in prefs.
-const char kCategoryContentId[] = "id";
-const char kCategoryContentTitle[] = "title";
-const char kCategoryContentProvidedByServer[] = "provided_by_server";
-const char kCategoryContentAllowFetchingMore[] = "allow_fetching_more";
-
-// TODO(treib): Remove after M57.
-const char kDeprecatedSnippetHostsPref[] = "ntp_snippets.hosts";
-
-base::TimeDelta GetFetchingInterval(bool is_wifi,
-                                    UserClassifier::UserClass user_class) {
-  double value_hours = 0.0;
-
-  const int index = is_wifi ? 1 : 0;
-  const char* param_name = "";
-  switch (user_class) {
-    case UserClassifier::UserClass::RARE_NTP_USER:
-      value_hours = kDefaultFetchingIntervalRareNtpUser[index];
-      param_name = kFetchingIntervalParamNameRareNtpUser[index];
-      break;
-    case UserClassifier::UserClass::ACTIVE_NTP_USER:
-      value_hours = kDefaultFetchingIntervalActiveNtpUser[index];
-      param_name = kFetchingIntervalParamNameActiveNtpUser[index];
-      break;
-    case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER:
-      value_hours = kDefaultFetchingIntervalActiveSuggestionsConsumer[index];
-      param_name = kFetchingIntervalParamNameActiveSuggestionsConsumer[index];
-      break;
-  }
-
-  // The default value can be overridden by a variation parameter.
-  std::string param_value_str = variations::GetVariationParamValueByFeature(
-      ntp_snippets::kArticleSuggestionsFeature, param_name);
-  if (!param_value_str.empty()) {
-    double param_value_hours = 0.0;
-    if (base::StringToDouble(param_value_str, &param_value_hours)) {
-      value_hours = param_value_hours;
-    } else {
-      LOG(WARNING) << "Invalid value for variation parameter " << param_name;
-    }
-  }
-
-  return base::TimeDelta::FromSecondsD(value_hours * 3600.0);
-}
-
-std::unique_ptr<std::vector<std::string>> GetSnippetIDVector(
-    const NTPSnippet::PtrVector& snippets) {
-  auto result = base::MakeUnique<std::vector<std::string>>();
-  for (const auto& snippet : snippets) {
-    result->push_back(snippet->id());
-  }
-  return result;
-}
-
-bool HasIntersection(const std::vector<std::string>& a,
-                     const std::set<std::string>& b) {
-  for (const std::string& item : a) {
-    if (base::ContainsValue(b, item)) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void EraseByPrimaryID(NTPSnippet::PtrVector* snippets,
-                      const std::vector<std::string>& ids) {
-  std::set<std::string> ids_lookup(ids.begin(), ids.end());
-  snippets->erase(
-      std::remove_if(snippets->begin(), snippets->end(),
-                     [&ids_lookup](const std::unique_ptr<NTPSnippet>& snippet) {
-                       return base::ContainsValue(ids_lookup, snippet->id());
-                     }),
-      snippets->end());
-}
-
-void EraseMatchingSnippets(NTPSnippet::PtrVector* snippets,
-                           const NTPSnippet::PtrVector& compare_against) {
-  std::set<std::string> compare_against_ids;
-  for (const std::unique_ptr<NTPSnippet>& snippet : compare_against) {
-    const std::vector<std::string>& snippet_ids = snippet->GetAllIDs();
-    compare_against_ids.insert(snippet_ids.begin(), snippet_ids.end());
-  }
-  snippets->erase(
-      std::remove_if(
-          snippets->begin(), snippets->end(),
-          [&compare_against_ids](const std::unique_ptr<NTPSnippet>& snippet) {
-            return HasIntersection(snippet->GetAllIDs(), compare_against_ids);
-          }),
-      snippets->end());
-}
-
-void RemoveNullPointers(NTPSnippet::PtrVector* snippets) {
-  snippets->erase(
-      std::remove_if(
-          snippets->begin(), snippets->end(),
-          [](const std::unique_ptr<NTPSnippet>& snippet) { return !snippet; }),
-      snippets->end());
-}
-
-void RemoveIncompleteSnippets(NTPSnippet::PtrVector* snippets) {
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kAddIncompleteSnippets)) {
-    return;
-  }
-  int num_snippets = snippets->size();
-  // Remove snippets that do not have all the info we need to display it to
-  // the user.
-  snippets->erase(
-      std::remove_if(snippets->begin(), snippets->end(),
-                     [](const std::unique_ptr<NTPSnippet>& snippet) {
-                       return !snippet->is_complete();
-                     }),
-      snippets->end());
-  int num_snippets_removed = num_snippets - snippets->size();
-  UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch",
-                        num_snippets_removed > 0);
-  if (num_snippets_removed > 0) {
-    UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets",
-                                num_snippets_removed);
-  }
-}
-
-std::vector<ContentSuggestion> ConvertToContentSuggestions(
-    Category category,
-    const NTPSnippet::PtrVector& snippets) {
-  std::vector<ContentSuggestion> result;
-  for (const std::unique_ptr<NTPSnippet>& snippet : snippets) {
-    // TODO(sfiera): if a snippet is not going to be displayed, move it
-    // directly to content.dismissed on fetch. Otherwise, we might prune
-    // other snippets to get down to kMaxSnippetCount, only to hide one of the
-    // incomplete ones we kept.
-    if (!snippet->is_complete()) {
-      continue;
-    }
-    GURL url = snippet->url();
-    if (base::FeatureList::IsEnabled(kPreferAmpUrlsFeature) &&
-        !snippet->amp_url().is_empty()) {
-      url = snippet->amp_url();
-    }
-    ContentSuggestion suggestion(category, snippet->id(), url);
-    suggestion.set_title(base::UTF8ToUTF16(snippet->title()));
-    suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet()));
-    suggestion.set_publish_date(snippet->publish_date());
-    suggestion.set_publisher_name(base::UTF8ToUTF16(snippet->publisher_name()));
-    suggestion.set_score(snippet->score());
-    result.emplace_back(std::move(suggestion));
-  }
-  return result;
-}
-
-void CallWithEmptyResults(const FetchDoneCallback& callback,
-                          const Status& status) {
-  if (callback.is_null()) {
-    return;
-  }
-  callback.Run(status, std::vector<ContentSuggestion>());
-}
-
-}  // namespace
-
-CachedImageFetcher::CachedImageFetcher(
-    std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
-    std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
-    PrefService* pref_service,
-    RemoteSuggestionsDatabase* database)
-    : image_fetcher_(std::move(image_fetcher)),
-      image_decoder_(std::move(image_decoder)),
-      database_(database),
-      thumbnail_requests_throttler_(
-          pref_service,
-          RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) {
-  // |image_fetcher_| can be null in tests.
-  if (image_fetcher_) {
-    image_fetcher_->SetImageFetcherDelegate(this);
-    image_fetcher_->SetDataUseServiceName(
-        data_use_measurement::DataUseUserData::NTP_SNIPPETS);
-  }
-}
-
-CachedImageFetcher::~CachedImageFetcher() {}
-
-void CachedImageFetcher::FetchSuggestionImage(
-    const ContentSuggestion::ID& suggestion_id,
-    const GURL& url,
-    const ImageFetchedCallback& callback) {
-  database_->LoadImage(
-      suggestion_id.id_within_category(),
-      base::Bind(&CachedImageFetcher::OnSnippetImageFetchedFromDatabase,
-                 base::Unretained(this), callback, suggestion_id, url));
-}
-
-// This function gets only called for caching the image data received from the
-// network. The actual decoding is done in OnSnippetImageDecodedFromDatabase().
-void CachedImageFetcher::OnImageDataFetched(
-    const std::string& id_within_category,
-    const std::string& image_data) {
-  if (image_data.empty()) {
-    return;
-  }
-  database_->SaveImage(id_within_category, image_data);
-}
-
-void CachedImageFetcher::OnImageDecodingDone(
-    const ImageFetchedCallback& callback,
-    const std::string& id_within_category,
-    const gfx::Image& image) {
-  callback.Run(image);
-}
-
-void CachedImageFetcher::OnSnippetImageFetchedFromDatabase(
-    const ImageFetchedCallback& callback,
-    const ContentSuggestion::ID& suggestion_id,
-    const GURL& url,
-    std::string data) {  // SnippetImageCallback requires nonconst reference.
-  // |image_decoder_| is null in tests.
-  if (image_decoder_ && !data.empty()) {
-    image_decoder_->DecodeImage(
-        data, base::Bind(
-                  &CachedImageFetcher::OnSnippetImageDecodedFromDatabase,
-                  base::Unretained(this), callback, suggestion_id, url));
-    return;
-  }
-  // Fetching from the DB failed; start a network fetch.
-  FetchSnippetImageFromNetwork(suggestion_id, url, callback);
-}
-
-void CachedImageFetcher::OnSnippetImageDecodedFromDatabase(
-    const ImageFetchedCallback& callback,
-    const ContentSuggestion::ID& suggestion_id,
-    const GURL& url,
-    const gfx::Image& image) {
-  if (!image.IsEmpty()) {
-    callback.Run(image);
-    return;
-  }
-  // If decoding the image failed, delete the DB entry.
-  database_->DeleteImage(suggestion_id.id_within_category());
-  FetchSnippetImageFromNetwork(suggestion_id, url, callback);
-}
-
-void CachedImageFetcher::FetchSnippetImageFromNetwork(
-    const ContentSuggestion::ID& suggestion_id,
-    const GURL& url,
-    const ImageFetchedCallback& callback) {
-  if (url.is_empty() ||
-      !thumbnail_requests_throttler_.DemandQuotaForRequest(
-          /*interactive_request=*/true)) {
-    // Return an empty image. Directly, this is never synchronous with the
-    // original FetchSuggestionImage() call - an asynchronous database query has
-    // happened in the meantime.
-    callback.Run(gfx::Image());
-    return;
-  }
-
-  image_fetcher_->StartOrQueueNetworkRequest(
-      suggestion_id.id_within_category(), url,
-      base::Bind(&CachedImageFetcher::OnImageDecodingDone,
-                 base::Unretained(this), callback));
-}
-
-RemoteSuggestionsProvider::RemoteSuggestionsProvider(
-    Observer* observer,
-    PrefService* pref_service,
-    const std::string& application_language_code,
-    CategoryRanker* category_ranker,
-    const UserClassifier* user_classifier,
-    NTPSnippetsScheduler* scheduler,
-    std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher,
-    std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
-    std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
-    std::unique_ptr<RemoteSuggestionsDatabase> database,
-    std::unique_ptr<RemoteSuggestionsStatusService> status_service)
-    : ContentSuggestionsProvider(observer),
-      state_(State::NOT_INITED),
-      pref_service_(pref_service),
-      articles_category_(
-          Category::FromKnownCategory(KnownCategories::ARTICLES)),
-      application_language_code_(application_language_code),
-      category_ranker_(category_ranker),
-      user_classifier_(user_classifier),
-      scheduler_(scheduler),
-      snippets_fetcher_(std::move(snippets_fetcher)),
-      database_(std::move(database)),
-      image_fetcher_(std::move(image_fetcher),
-                     std::move(image_decoder),
-                     pref_service,
-                     database_.get()),
-      status_service_(std::move(status_service)),
-      fetch_when_ready_(false),
-      nuke_when_initialized_(false),
-      clock_(base::MakeUnique<base::DefaultClock>()) {
-  pref_service_->ClearPref(kDeprecatedSnippetHostsPref);
-
-  RestoreCategoriesFromPrefs();
-  // The articles category always exists. Add it if we didn't get it from prefs.
-  // TODO(treib): Rethink this.
-  category_contents_.insert(
-      std::make_pair(articles_category_,
-                     CategoryContent(BuildArticleCategoryInfo(base::nullopt))));
-  // Tell the observer about all the categories.
-  for (const auto& entry : category_contents_) {
-    observer->OnCategoryStatusChanged(this, entry.first, entry.second.status);
-  }
-
-  if (database_->IsErrorState()) {
-    EnterState(State::ERROR_OCCURRED);
-    UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR);
-    return;
-  }
-
-  database_->SetErrorCallback(base::Bind(
-      &RemoteSuggestionsProvider::OnDatabaseError, base::Unretained(this)));
-
-  // We transition to other states while finalizing the initialization, when the
-  // database is done loading.
-  database_load_start_ = base::TimeTicks::Now();
-  database_->LoadSnippets(base::Bind(
-      &RemoteSuggestionsProvider::OnDatabaseLoaded, base::Unretained(this)));
-}
+RemoteSuggestionsProvider::RemoteSuggestionsProvider(Observer* observer)
+    : ContentSuggestionsProvider(observer) {}
 
 RemoteSuggestionsProvider::~RemoteSuggestionsProvider() = default;
 
-// static
-void RemoteSuggestionsProvider::RegisterProfilePrefs(
-    PrefRegistrySimple* registry) {
-  // TODO(treib): Remove after M57.
-  registry->RegisterListPref(kDeprecatedSnippetHostsPref);
-  registry->RegisterListPref(prefs::kRemoteSuggestionCategories);
-  registry->RegisterInt64Pref(prefs::kSnippetBackgroundFetchingIntervalWifi, 0);
-  registry->RegisterInt64Pref(prefs::kSnippetBackgroundFetchingIntervalFallback,
-                              0);
-  registry->RegisterInt64Pref(prefs::kLastSuccessfulBackgroundFetchTime, 0);
-
-  RemoteSuggestionsStatusService::RegisterProfilePrefs(registry);
-}
-
-void RemoteSuggestionsProvider::FetchSnippetsInTheBackground() {
-  FetchSnippets(/*interactive_request=*/false);
-}
-
-void RemoteSuggestionsProvider::FetchSnippetsForAllCategories() {
-  // TODO(markusheintz): Investigate whether we can call the Fetch method
-  // instead of the FetchSnippets.
-  FetchSnippets(/*interactive_request=*/true);
-}
-
-void RemoteSuggestionsProvider::FetchSnippets(
-    bool interactive_request) {
-  if (!ready()) {
-    fetch_when_ready_ = true;
-    return;
-  }
-
-  MarkEmptyCategoriesAsLoading();
-
-  NTPSnippetsRequestParams params = BuildFetchParams();
-  params.interactive_request = interactive_request;
-  snippets_fetcher_->FetchSnippets(
-      params, base::BindOnce(&RemoteSuggestionsProvider::OnFetchFinished,
-                             base::Unretained(this), interactive_request));
-}
-
-void RemoteSuggestionsProvider::Fetch(
-    const Category& category,
-    const std::set<std::string>& known_suggestion_ids,
-    const FetchDoneCallback& callback) {
-  if (!ready()) {
-    CallWithEmptyResults(callback,
-                         Status(StatusCode::TEMPORARY_ERROR,
-                                "RemoteSuggestionsProvider is not ready!"));
-    return;
-  }
-  NTPSnippetsRequestParams params = BuildFetchParams();
-  params.excluded_ids.insert(known_suggestion_ids.begin(),
-                             known_suggestion_ids.end());
-  params.interactive_request = true;
-  params.exclusive_category = category;
-
-  snippets_fetcher_->FetchSnippets(
-      params, base::BindOnce(&RemoteSuggestionsProvider::OnFetchMoreFinished,
-                             base::Unretained(this), callback));
-}
-
-// Builds default fetcher params.
-NTPSnippetsRequestParams RemoteSuggestionsProvider::BuildFetchParams() const {
-  NTPSnippetsRequestParams result;
-  result.language_code = application_language_code_;
-  result.count_to_fetch = kMaxSnippetCount;
-  for (const auto& map_entry : category_contents_) {
-    const CategoryContent& content = map_entry.second;
-    for (const auto& dismissed_snippet : content.dismissed) {
-      result.excluded_ids.insert(dismissed_snippet->id());
-    }
-  }
-  return result;
-}
-
-void RemoteSuggestionsProvider::MarkEmptyCategoriesAsLoading() {
-  for (const auto& item : category_contents_) {
-    Category category = item.first;
-    const CategoryContent& content = item.second;
-    if (content.snippets.empty()) {
-      UpdateCategoryStatus(category, CategoryStatus::AVAILABLE_LOADING);
-    }
-  }
-}
-
-void RemoteSuggestionsProvider::RescheduleFetching(bool force) {
-  // The scheduler only exists on Android so far, it's null on other platforms.
-  if (!scheduler_) {
-    return;
-  }
-
-  if (ready()) {
-    base::TimeDelta old_interval_wifi = base::TimeDelta::FromInternalValue(
-        pref_service_->GetInt64(prefs::kSnippetBackgroundFetchingIntervalWifi));
-    base::TimeDelta old_interval_fallback =
-        base::TimeDelta::FromInternalValue(pref_service_->GetInt64(
-            prefs::kSnippetBackgroundFetchingIntervalFallback));
-    UserClassifier::UserClass user_class = user_classifier_->GetUserClass();
-    base::TimeDelta interval_wifi =
-        GetFetchingInterval(/*is_wifi=*/true, user_class);
-    base::TimeDelta interval_fallback =
-        GetFetchingInterval(/*is_wifi=*/false, user_class);
-    if (force || interval_wifi != old_interval_wifi ||
-        interval_fallback != old_interval_fallback) {
-      scheduler_->Schedule(interval_wifi, interval_fallback);
-      pref_service_->SetInt64(prefs::kSnippetBackgroundFetchingIntervalWifi,
-                              interval_wifi.ToInternalValue());
-      pref_service_->SetInt64(prefs::kSnippetBackgroundFetchingIntervalFallback,
-                              interval_fallback.ToInternalValue());
-    }
-  } else {
-    // If we're NOT_INITED, we don't know whether to schedule or unschedule.
-    // If |force| is false, all is well: We'll reschedule on the next state
-    // change anyway. If it's true, then unschedule here, to make sure that the
-    // next reschedule actually happens.
-    if (state_ != State::NOT_INITED || force) {
-      scheduler_->Unschedule();
-      pref_service_->ClearPref(prefs::kSnippetBackgroundFetchingIntervalWifi);
-      pref_service_->ClearPref(
-          prefs::kSnippetBackgroundFetchingIntervalFallback);
-    }
-  }
-}
-
-CategoryStatus RemoteSuggestionsProvider::GetCategoryStatus(Category category) {
-  auto content_it = category_contents_.find(category);
-  DCHECK(content_it != category_contents_.end());
-  return content_it->second.status;
-}
-
-CategoryInfo RemoteSuggestionsProvider::GetCategoryInfo(Category category) {
-  auto content_it = category_contents_.find(category);
-  DCHECK(content_it != category_contents_.end());
-  return content_it->second.info;
-}
-
-void RemoteSuggestionsProvider::DismissSuggestion(
-    const ContentSuggestion::ID& suggestion_id) {
-  if (!ready()) {
-    return;
-  }
-
-  auto content_it = category_contents_.find(suggestion_id.category());
-  DCHECK(content_it != category_contents_.end());
-  CategoryContent* content = &content_it->second;
-  DismissSuggestionFromCategoryContent(content,
-                                       suggestion_id.id_within_category());
-}
-
-void RemoteSuggestionsProvider::ClearHistory(
-    base::Time begin,
-    base::Time end,
-    const base::Callback<bool(const GURL& url)>& filter) {
-  // Both time range and the filter are ignored and all suggestions are removed,
-  // because it is not known which history entries were used for the suggestions
-  // personalization.
-  if (!ready()) {
-    nuke_when_initialized_ = true;
-  } else {
-    NukeAllSnippets();
-  }
-}
-
-void RemoteSuggestionsProvider::ClearCachedSuggestions(Category category) {
-  if (!initialized()) {
-    return;
-  }
-
-  auto content_it = category_contents_.find(category);
-  if (content_it == category_contents_.end()) {
-    return;
-  }
-  CategoryContent* content = &content_it->second;
-  if (content->snippets.empty()) {
-    return;
-  }
-
-  database_->DeleteSnippets(GetSnippetIDVector(content->snippets));
-  database_->DeleteImages(GetSnippetIDVector(content->snippets));
-  content->snippets.clear();
-
-  if (IsCategoryStatusAvailable(content->status)) {
-    NotifyNewSuggestions(category, *content);
-  }
-}
-
-void RemoteSuggestionsProvider::OnSignInStateChanged() {
-  // Make sure the status service is registered and we already initialised its
-  // start state.
-  if (!initialized()) {
-    return;
-  }
-
-  status_service_->OnSignInStateChanged();
-}
-
-void RemoteSuggestionsProvider::GetDismissedSuggestionsForDebugging(
-    Category category,
-    const DismissedSuggestionsCallback& callback) {
-  auto content_it = category_contents_.find(category);
-  DCHECK(content_it != category_contents_.end());
-  callback.Run(
-      ConvertToContentSuggestions(category, content_it->second.dismissed));
-}
-
-void RemoteSuggestionsProvider::ClearDismissedSuggestionsForDebugging(
-    Category category) {
-  auto content_it = category_contents_.find(category);
-  DCHECK(content_it != category_contents_.end());
-  CategoryContent* content = &content_it->second;
-
-  if (!initialized()) {
-    return;
-  }
-
-  if (content->dismissed.empty()) {
-    return;
-  }
-
-  database_->DeleteSnippets(GetSnippetIDVector(content->dismissed));
-  // The image got already deleted when the suggestion was dismissed.
-
-  content->dismissed.clear();
-}
-
-// static
-int RemoteSuggestionsProvider::GetMaxSnippetCountForTesting() {
-  return kMaxSnippetCount;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Private methods
-
-GURL RemoteSuggestionsProvider::FindSnippetImageUrl(
-    const ContentSuggestion::ID& suggestion_id) const {
-  DCHECK(base::ContainsKey(category_contents_, suggestion_id.category()));
-
-  const CategoryContent& content =
-      category_contents_.at(suggestion_id.category());
-  const NTPSnippet* snippet =
-      content.FindSnippet(suggestion_id.id_within_category());
-  if (!snippet) {
-    return GURL();
-  }
-  return snippet->salient_image_url();
-}
-
-void RemoteSuggestionsProvider::OnDatabaseLoaded(
-    NTPSnippet::PtrVector snippets) {
-  if (state_ == State::ERROR_OCCURRED) {
-    return;
-  }
-  DCHECK(state_ == State::NOT_INITED);
-  DCHECK(base::ContainsKey(category_contents_, articles_category_));
-
-  base::TimeDelta database_load_time =
-      base::TimeTicks::Now() - database_load_start_;
-  UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Snippets.DatabaseLoadTime",
-                             database_load_time);
-
-  NTPSnippet::PtrVector to_delete;
-  for (std::unique_ptr<NTPSnippet>& snippet : snippets) {
-    Category snippet_category =
-        Category::FromRemoteCategory(snippet->remote_category_id());
-    auto content_it = category_contents_.find(snippet_category);
-    // We should already know about the category.
-    if (content_it == category_contents_.end()) {
-      DLOG(WARNING) << "Loaded a suggestion for unknown category "
-                    << snippet_category << " from the DB; deleting";
-      to_delete.emplace_back(std::move(snippet));
-      continue;
-    }
-    CategoryContent* content = &content_it->second;
-    if (snippet->is_dismissed()) {
-      content->dismissed.emplace_back(std::move(snippet));
-    } else {
-      content->snippets.emplace_back(std::move(snippet));
-    }
-  }
-  if (!to_delete.empty()) {
-    database_->DeleteSnippets(GetSnippetIDVector(to_delete));
-    database_->DeleteImages(GetSnippetIDVector(to_delete));
-  }
-
-  // Sort the suggestions in each category.
-  // TODO(treib): Persist the actual order in the DB somehow? crbug.com/654409
-  for (auto& entry : category_contents_) {
-    CategoryContent* content = &entry.second;
-    std::sort(content->snippets.begin(), content->snippets.end(),
-              [](const std::unique_ptr<NTPSnippet>& lhs,
-                 const std::unique_ptr<NTPSnippet>& rhs) {
-                return lhs->score() > rhs->score();
-              });
-  }
-
-  // TODO(tschumann): If I move ClearExpiredDismissedSnippets() to the beginning
-  // of the function, it essentially does nothing but tests are still green. Fix
-  // this!
-  ClearExpiredDismissedSnippets();
-  ClearOrphanedImages();
-  FinishInitialization();
-}
-
-void RemoteSuggestionsProvider::OnDatabaseError() {
-  EnterState(State::ERROR_OCCURRED);
-  UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR);
-}
-
-void RemoteSuggestionsProvider::OnFetchMoreFinished(
-    const FetchDoneCallback& fetching_callback,
-    Status status,
-    NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories) {
-  if (!fetched_categories) {
-    DCHECK(!status.IsSuccess());
-    CallWithEmptyResults(fetching_callback, status);
-    return;
-  }
-  if (fetched_categories->size() != 1u) {
-    LOG(DFATAL) << "Requested one exclusive category but received "
-                << fetched_categories->size() << " categories.";
-    CallWithEmptyResults(fetching_callback,
-                         Status(StatusCode::PERMANENT_ERROR,
-                                "RemoteSuggestionsProvider received more "
-                                "categories than requested."));
-    return;
-  }
-  auto& fetched_category = (*fetched_categories)[0];
-  Category category = fetched_category.category;
-  CategoryContent* existing_content =
-      UpdateCategoryInfo(category, fetched_category.info);
-  SanitizeReceivedSnippets(existing_content->dismissed,
-                           &fetched_category.snippets);
-  // We compute the result now before modifying |fetched_category.snippets|.
-  // However, we wait with notifying the caller until the end of the method when
-  // all state is updated.
-  std::vector<ContentSuggestion> result =
-      ConvertToContentSuggestions(category, fetched_category.snippets);
-
-  // Fill up the newly fetched snippets with existing ones, store them, and
-  // notify observers about new data.
-  while (fetched_category.snippets.size() <
-             static_cast<size_t>(kMaxSnippetCount) &&
-         !existing_content->snippets.empty()) {
-    fetched_category.snippets.emplace(
-        fetched_category.snippets.begin(),
-        std::move(existing_content->snippets.back()));
-    existing_content->snippets.pop_back();
-  }
-  std::vector<std::string> to_dismiss =
-      *GetSnippetIDVector(existing_content->snippets);
-  for (const auto& id : to_dismiss) {
-    DismissSuggestionFromCategoryContent(existing_content, id);
-  }
-  DCHECK(existing_content->snippets.empty());
-
-  IntegrateSnippets(existing_content, std::move(fetched_category.snippets));
-
-  // TODO(tschumann): We should properly honor the existing category state,
-  // e.g. to make sure we don't serve results after the sign-out. Revisit this
-  // once the snippets fetcher supports concurrent requests. We can then see if
-  // Nuke should also cancel outstanding requests or we want to check the
-  // status.
-  UpdateCategoryStatus(category, CategoryStatus::AVAILABLE);
-  // Notify callers and observers.
-  fetching_callback.Run(Status::Success(), std::move(result));
-  NotifyNewSuggestions(category, *existing_content);
-}
-
-void RemoteSuggestionsProvider::OnFetchFinished(
-    bool interactive_request,
-    Status status,
-    NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories) {
-  if (!ready()) {
-    // TODO(tschumann): What happens if this was a user-triggered, interactive
-    // request? Is the UI waiting indefinitely now?
-    return;
-  }
-
-  // Record the fetch time of a successfull background fetch.
-  if (!interactive_request && status.IsSuccess()) {
-    pref_service_->SetInt64(prefs::kLastSuccessfulBackgroundFetchTime,
-                            clock_->Now().ToInternalValue());
-  }
-
-  // Mark all categories as not provided by the server in the latest fetch. The
-  // ones we got will be marked again below.
-  for (auto& item : category_contents_) {
-    CategoryContent* content = &item.second;
-    content->included_in_last_server_response = false;
-  }
-
-  // Clear up expired dismissed snippets before we use them to filter new ones.
-  ClearExpiredDismissedSnippets();
-
-  // If snippets were fetched successfully, update our |category_contents_| from
-  // each category provided by the server.
-  if (fetched_categories) {
-    // TODO(treib): Reorder |category_contents_| to match the order we received
-    // from the server. crbug.com/653816
-    for (NTPSnippetsFetcher::FetchedCategory& fetched_category :
-         *fetched_categories) {
-      // TODO(tschumann): Remove this histogram once we only talk to the content
-      // suggestions cloud backend.
-      if (fetched_category.category == articles_category_) {
-        UMA_HISTOGRAM_SPARSE_SLOWLY(
-            "NewTabPage.Snippets.NumArticlesFetched",
-            std::min(fetched_category.snippets.size(),
-                     static_cast<size_t>(kMaxSnippetCount + 1)));
-      }
-      category_ranker_->AppendCategoryIfNecessary(fetched_category.category);
-      CategoryContent* content =
-          UpdateCategoryInfo(fetched_category.category, fetched_category.info);
-      content->included_in_last_server_response = true;
-      SanitizeReceivedSnippets(content->dismissed, &fetched_category.snippets);
-      IntegrateSnippets(content, std::move(fetched_category.snippets));
-    }
-  }
-
-  // TODO(tschumann): The snippets fetcher needs to signal errors so that we
-  // know why we received no data. If an error occured, none of the following
-  // should take place.
-
-  // We might have gotten new categories (or updated the titles of existing
-  // ones), so update the pref.
-  StoreCategoriesToPrefs();
-
-  for (const auto& item : category_contents_) {
-    Category category = item.first;
-    UpdateCategoryStatus(category, CategoryStatus::AVAILABLE);
-    // TODO(sfiera): notify only when a category changed above.
-    NotifyNewSuggestions(category, item.second);
-  }
-
-  // TODO(sfiera): equivalent metrics for non-articles.
-  auto content_it = category_contents_.find(articles_category_);
-  DCHECK(content_it != category_contents_.end());
-  const CategoryContent& content = content_it->second;
-  UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles",
-                              content.snippets.size());
-  if (content.snippets.empty() && !content.dismissed.empty()) {
-    UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded",
-                         content.dismissed.size());
-  }
-
-  // Reschedule after a successful fetch. This resets all currently scheduled
-  // fetches, to make sure the fallback interval triggers only if no wifi fetch
-  // succeeded, and also that we don't do a background fetch immediately after
-  // a user-initiated one.
-  if (fetched_categories) {
-    RescheduleFetching(true);
-  }
-}
-
-void RemoteSuggestionsProvider::ArchiveSnippets(
-    CategoryContent* content,
-    NTPSnippet::PtrVector* to_archive) {
-  // Archive previous snippets - move them at the beginning of the list.
-  content->archived.insert(content->archived.begin(),
-                           std::make_move_iterator(to_archive->begin()),
-                           std::make_move_iterator(to_archive->end()));
-  to_archive->clear();
-
-  // If there are more archived snippets than we want to keep, delete the
-  // oldest ones by their fetch time (which are always in the back).
-  if (content->archived.size() > kMaxArchivedSnippetCount) {
-    NTPSnippet::PtrVector to_delete(
-        std::make_move_iterator(content->archived.begin() +
-                                kMaxArchivedSnippetCount),
-        std::make_move_iterator(content->archived.end()));
-    content->archived.resize(kMaxArchivedSnippetCount);
-    database_->DeleteImages(GetSnippetIDVector(to_delete));
-  }
-}
-
-void RemoteSuggestionsProvider::SanitizeReceivedSnippets(
-    const NTPSnippet::PtrVector& dismissed,
-    NTPSnippet::PtrVector* snippets) {
-  DCHECK(ready());
-  EraseMatchingSnippets(snippets, dismissed);
-  RemoveIncompleteSnippets(snippets);
-}
-
-void RemoteSuggestionsProvider::IntegrateSnippets(
-    CategoryContent* content,
-    NTPSnippet::PtrVector new_snippets) {
-  DCHECK(ready());
-
-  // Do not touch the current set of snippets if the newly fetched one is empty.
-  // TODO(tschumann): This should go. If we get empty results we should update
-  // accordingly and remove the old one (only of course if this was not received
-  // through a fetch-more).
-  if (new_snippets.empty()) {
-    return;
-  }
-
-  // It's entirely possible that the newly fetched snippets contain articles
-  // that have been present before.
-  // We need to make sure to only delete and archive snippets that don't
-  // appear with the same ID in the new suggestions (it's fine for additional
-  // IDs though).
-  EraseByPrimaryID(&content->snippets, *GetSnippetIDVector(new_snippets));
-  // Do not delete the thumbnail images as they are still handy on open NTPs.
-  database_->DeleteSnippets(GetSnippetIDVector(content->snippets));
-  // Note, that ArchiveSnippets will clear |content->snippets|.
-  ArchiveSnippets(content, &content->snippets);
-
-  database_->SaveSnippets(new_snippets);
-
-  content->snippets = std::move(new_snippets);
-}
-
-void RemoteSuggestionsProvider::DismissSuggestionFromCategoryContent(
-    CategoryContent* content,
-    const std::string& id_within_category) {
-  auto it = std::find_if(
-      content->snippets.begin(), content->snippets.end(),
-      [&id_within_category](const std::unique_ptr<NTPSnippet>& snippet) {
-        return snippet->id() == id_within_category;
-      });
-  if (it == content->snippets.end()) {
-    return;
-  }
-
-  (*it)->set_dismissed(true);
-
-  database_->SaveSnippet(**it);
-
-  content->dismissed.push_back(std::move(*it));
-  content->snippets.erase(it);
-}
-
-void RemoteSuggestionsProvider::ClearExpiredDismissedSnippets() {
-  std::vector<Category> categories_to_erase;
-
-  const base::Time now = base::Time::Now();
-
-  for (auto& item : category_contents_) {
-    Category category = item.first;
-    CategoryContent* content = &item.second;
-
-    NTPSnippet::PtrVector to_delete;
-    // Move expired dismissed snippets over into |to_delete|.
-    for (std::unique_ptr<NTPSnippet>& snippet : content->dismissed) {
-      if (snippet->expiry_date() <= now) {
-        to_delete.emplace_back(std::move(snippet));
-      }
-    }
-    RemoveNullPointers(&content->dismissed);
-
-    // Delete the images.
-    database_->DeleteImages(GetSnippetIDVector(to_delete));
-    // Delete the removed article suggestions from the DB.
-    database_->DeleteSnippets(GetSnippetIDVector(to_delete));
-
-    if (content->snippets.empty() && content->dismissed.empty() &&
-        category != articles_category_ &&
-        !content->included_in_last_server_response) {
-      categories_to_erase.push_back(category);
-    }
-  }
-
-  for (Category category : categories_to_erase) {
-    UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
-    category_contents_.erase(category);
-  }
-
-  StoreCategoriesToPrefs();
-}
-
-void RemoteSuggestionsProvider::ClearOrphanedImages() {
-  auto alive_snippets = base::MakeUnique<std::set<std::string>>();
-  for (const auto& entry : category_contents_) {
-    const CategoryContent& content = entry.second;
-    for (const auto& snippet_ptr : content.snippets) {
-      alive_snippets->insert(snippet_ptr->id());
-    }
-    for (const auto& snippet_ptr : content.dismissed) {
-      alive_snippets->insert(snippet_ptr->id());
-    }
-  }
-  database_->GarbageCollectImages(std::move(alive_snippets));
-}
-
-void RemoteSuggestionsProvider::NukeAllSnippets() {
-  std::vector<Category> categories_to_erase;
-
-  // Empty the ARTICLES category and remove all others, since they may or may
-  // not be personalized.
-  for (const auto& item : category_contents_) {
-    Category category = item.first;
-
-    ClearCachedSuggestions(category);
-    ClearDismissedSuggestionsForDebugging(category);
-
-    UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
-
-    // Remove the category entirely; it may or may not reappear.
-    if (category != articles_category_) {
-      categories_to_erase.push_back(category);
-    }
-  }
-
-  for (Category category : categories_to_erase) {
-    category_contents_.erase(category);
-  }
-
-  StoreCategoriesToPrefs();
-}
-
-void RemoteSuggestionsProvider::FetchSuggestionImage(
-    const ContentSuggestion::ID& suggestion_id,
-    const ImageFetchedCallback& callback) {
-  if (!base::ContainsKey(category_contents_, suggestion_id.category())) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::Bind(callback, gfx::Image()));
-    return;
-  }
-  GURL image_url = FindSnippetImageUrl(suggestion_id);
-  if (image_url.is_empty()) {
-    // As we don't know the corresponding snippet anymore, we don't expect to
-    // find it in the database (and also can't fetch it remotely). Cut the
-    // lookup short and return directly.
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::Bind(callback, gfx::Image()));
-    return;
-  }
-  image_fetcher_.FetchSuggestionImage(suggestion_id, image_url, callback);
-}
-
-void RemoteSuggestionsProvider::EnterStateReady() {
-  if (nuke_when_initialized_) {
-    NukeAllSnippets();
-    nuke_when_initialized_ = false;
-  }
-
-  auto article_category_it = category_contents_.find(articles_category_);
-  DCHECK(article_category_it != category_contents_.end());
-  if (article_category_it->second.snippets.empty() || fetch_when_ready_) {
-    // TODO(jkrcal): Fetching snippets automatically upon creation of this
-    // lazily created service can cause troubles, e.g. in unit tests where
-    // network I/O is not allowed.
-    // Either add a DCHECK here that we actually are allowed to do network I/O
-    // or change the logic so that some explicit call is always needed for the
-    // network request.
-    FetchSnippets(/*interactive_request=*/false);
-    fetch_when_ready_ = false;
-  }
-
-  for (const auto& item : category_contents_) {
-    Category category = item.first;
-    const CategoryContent& content = item.second;
-    // FetchSnippets has set the status to |AVAILABLE_LOADING| if relevant,
-    // otherwise we transition to |AVAILABLE| here.
-    if (content.status != CategoryStatus::AVAILABLE_LOADING) {
-      UpdateCategoryStatus(category, CategoryStatus::AVAILABLE);
-    }
-  }
-}
-
-void RemoteSuggestionsProvider::EnterStateDisabled() {
-  NukeAllSnippets();
-}
-
-void RemoteSuggestionsProvider::EnterStateError() {
-  status_service_.reset();
-}
-
-void RemoteSuggestionsProvider::FinishInitialization() {
-  if (nuke_when_initialized_) {
-    // We nuke here in addition to EnterStateReady, so that it happens even if
-    // we enter the DISABLED state below.
-    NukeAllSnippets();
-    nuke_when_initialized_ = false;
-  }
-
-  // Note: Initializing the status service will run the callback right away with
-  // the current state.
-  status_service_->Init(base::Bind(&RemoteSuggestionsProvider::OnStatusChanged,
-                                   base::Unretained(this)));
-
-  // Always notify here even if we got nothing from the database, because we
-  // don't know how long the fetch will take or if it will even complete.
-  for (const auto& item : category_contents_) {
-    Category category = item.first;
-    const CategoryContent& content = item.second;
-    // Note: We might be in a non-available status here, e.g. DISABLED due to
-    // enterprise policy.
-    if (IsCategoryStatusAvailable(content.status)) {
-      NotifyNewSuggestions(category, content);
-    }
-  }
-}
-
-void RemoteSuggestionsProvider::OnStatusChanged(
-    RemoteSuggestionsStatus old_status,
-    RemoteSuggestionsStatus new_status) {
-  switch (new_status) {
-    case RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN:
-      if (old_status == RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT) {
-        DCHECK(state_ == State::READY);
-        // Clear nonpersonalized suggestions.
-        NukeAllSnippets();
-        // Fetch personalized ones.
-        FetchSnippets(/*interactive_request=*/true);
-      } else {
-        // Do not change the status. That will be done in EnterStateReady().
-        EnterState(State::READY);
-      }
-      break;
-
-    case RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT:
-      if (old_status == RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN) {
-        DCHECK(state_ == State::READY);
-        // Clear personalized suggestions.
-        NukeAllSnippets();
-        // Fetch nonpersonalized ones.
-        FetchSnippets(/*interactive_request=*/true);
-      } else {
-        // Do not change the status. That will be done in EnterStateReady().
-        EnterState(State::READY);
-      }
-      break;
-
-    case RemoteSuggestionsStatus::EXPLICITLY_DISABLED:
-      EnterState(State::DISABLED);
-      UpdateAllCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
-      break;
-
-    case RemoteSuggestionsStatus::SIGNED_OUT_AND_DISABLED:
-      EnterState(State::DISABLED);
-      UpdateAllCategoryStatus(CategoryStatus::SIGNED_OUT);
-      break;
-  }
-}
-
-void RemoteSuggestionsProvider::EnterState(State state) {
-  if (state == state_) {
-    return;
-  }
-
-  UMA_HISTOGRAM_ENUMERATION("NewTabPage.Snippets.EnteredState",
-                            static_cast<int>(state),
-                            static_cast<int>(State::COUNT));
-
-  switch (state) {
-    case State::NOT_INITED:
-      // Initial state, it should not be possible to get back there.
-      NOTREACHED();
-      break;
-
-    case State::READY:
-      DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED);
-
-      DVLOG(1) << "Entering state: READY";
-      state_ = State::READY;
-      EnterStateReady();
-      break;
-
-    case State::DISABLED:
-      DCHECK(state_ == State::NOT_INITED || state_ == State::READY);
-
-      DVLOG(1) << "Entering state: DISABLED";
-      state_ = State::DISABLED;
-      EnterStateDisabled();
-      break;
-
-    case State::ERROR_OCCURRED:
-      DVLOG(1) << "Entering state: ERROR_OCCURRED";
-      state_ = State::ERROR_OCCURRED;
-      EnterStateError();
-      break;
-
-    case State::COUNT:
-      NOTREACHED();
-      break;
-  }
-
-  // Schedule or un-schedule background fetching after each state change.
-  RescheduleFetching(false);
-}
-
-void RemoteSuggestionsProvider::NotifyNewSuggestions(
-    Category category,
-    const CategoryContent& content) {
-  DCHECK(IsCategoryStatusAvailable(content.status));
-
-  std::vector<ContentSuggestion> result =
-      ConvertToContentSuggestions(category, content.snippets);
-
-  DVLOG(1) << "NotifyNewSuggestions(): " << result.size()
-           << " items in category " << category;
-  observer()->OnNewSuggestions(this, category, std::move(result));
-}
-
-void RemoteSuggestionsProvider::UpdateCategoryStatus(Category category,
-                                                     CategoryStatus status) {
-  auto content_it = category_contents_.find(category);
-  DCHECK(content_it != category_contents_.end());
-  CategoryContent& content = content_it->second;
-
-  if (status == content.status) {
-    return;
-  }
-
-  DVLOG(1) << "UpdateCategoryStatus(): " << category.id() << ": "
-           << static_cast<int>(content.status) << " -> "
-           << static_cast<int>(status);
-  content.status = status;
-  observer()->OnCategoryStatusChanged(this, category, content.status);
-}
-
-void RemoteSuggestionsProvider::UpdateAllCategoryStatus(CategoryStatus status) {
-  for (const auto& category : category_contents_) {
-    UpdateCategoryStatus(category.first, status);
-  }
-}
-
-namespace {
-
-template <typename T>
-typename T::const_iterator FindSnippetInContainer(
-    const T& container,
-    const std::string& id_within_category) {
-  return std::find_if(
-      container.begin(), container.end(),
-      [&id_within_category](const std::unique_ptr<NTPSnippet>& snippet) {
-        return snippet->id() == id_within_category;
-      });
-}
-
-}  // namespace
-
-const NTPSnippet* RemoteSuggestionsProvider::CategoryContent::FindSnippet(
-    const std::string& id_within_category) const {
-  // Search for the snippet in current and archived snippets.
-  auto it = FindSnippetInContainer(snippets, id_within_category);
-  if (it != snippets.end()) {
-    return it->get();
-  }
-  auto archived_it = FindSnippetInContainer(archived, id_within_category);
-  if (archived_it != archived.end()) {
-    return archived_it->get();
-  }
-  auto dismissed_it = FindSnippetInContainer(dismissed, id_within_category);
-  if (dismissed_it != dismissed.end()) {
-    return dismissed_it->get();
-  }
-  return nullptr;
-}
-
-RemoteSuggestionsProvider::CategoryContent*
-RemoteSuggestionsProvider::UpdateCategoryInfo(Category category,
-                                              const CategoryInfo& info) {
-  auto content_it = category_contents_.find(category);
-  if (content_it == category_contents_.end()) {
-    content_it = category_contents_
-                     .insert(std::make_pair(category, CategoryContent(info)))
-                     .first;
-  } else {
-    content_it->second.info = info;
-  }
-  return &content_it->second;
-}
-
-void RemoteSuggestionsProvider::RestoreCategoriesFromPrefs() {
-  // This must only be called at startup, before there are any categories.
-  DCHECK(category_contents_.empty());
-
-  const base::ListValue* list =
-      pref_service_->GetList(prefs::kRemoteSuggestionCategories);
-  for (const std::unique_ptr<base::Value>& entry : *list) {
-    const base::DictionaryValue* dict = nullptr;
-    if (!entry->GetAsDictionary(&dict)) {
-      DLOG(WARNING) << "Invalid category pref value: " << *entry;
-      continue;
-    }
-    int id = 0;
-    if (!dict->GetInteger(kCategoryContentId, &id)) {
-      DLOG(WARNING) << "Invalid category pref value, missing '"
-                    << kCategoryContentId << "': " << *entry;
-      continue;
-    }
-    base::string16 title;
-    if (!dict->GetString(kCategoryContentTitle, &title)) {
-      DLOG(WARNING) << "Invalid category pref value, missing '"
-                    << kCategoryContentTitle << "': " << *entry;
-      continue;
-    }
-    bool included_in_last_server_response = false;
-    if (!dict->GetBoolean(kCategoryContentProvidedByServer,
-                          &included_in_last_server_response)) {
-      DLOG(WARNING) << "Invalid category pref value, missing '"
-                    << kCategoryContentProvidedByServer << "': " << *entry;
-      continue;
-    }
-    bool allow_fetching_more_results = false;
-    // This wasn't always around, so it's okay if it's missing.
-    dict->GetBoolean(kCategoryContentAllowFetchingMore,
-                     &allow_fetching_more_results);
-
-    Category category = Category::FromIDValue(id);
-    // The ranker may not persist the order of remote categories.
-    category_ranker_->AppendCategoryIfNecessary(category);
-    // TODO(tschumann): The following has a bad smell that category
-    // serialization / deserialization should not be done inside this
-    // class. We should move that into a central place that also knows how to
-    // parse data we received from remote backends.
-    CategoryInfo info =
-        category == articles_category_
-            ? BuildArticleCategoryInfo(title)
-            : BuildRemoteCategoryInfo(title, allow_fetching_more_results);
-    CategoryContent* content = UpdateCategoryInfo(category, info);
-    content->included_in_last_server_response =
-        included_in_last_server_response;
-  }
-}
-
-void RemoteSuggestionsProvider::StoreCategoriesToPrefs() {
-  // Collect all the CategoryContents.
-  std::vector<std::pair<Category, const CategoryContent*>> to_store;
-  for (const auto& entry : category_contents_) {
-    to_store.emplace_back(entry.first, &entry.second);
-  }
-  // The ranker may not persist the order, thus, it is stored by the provider.
-  std::sort(to_store.begin(), to_store.end(),
-            [this](const std::pair<Category, const CategoryContent*>& left,
-                   const std::pair<Category, const CategoryContent*>& right) {
-              return category_ranker_->Compare(left.first, right.first);
-            });
-  // Convert the relevant info into a base::ListValue for storage.
-  base::ListValue list;
-  for (const auto& entry : to_store) {
-    const Category& category = entry.first;
-    const CategoryContent& content = *entry.second;
-    auto dict = base::MakeUnique<base::DictionaryValue>();
-    dict->SetInteger(kCategoryContentId, category.id());
-    // TODO(tschumann): Persist other properties of the CategoryInfo.
-    dict->SetString(kCategoryContentTitle, content.info.title());
-    dict->SetBoolean(kCategoryContentProvidedByServer,
-                     content.included_in_last_server_response);
-    dict->SetBoolean(kCategoryContentAllowFetchingMore,
-                     content.info.has_more_action());
-    list.Append(std::move(dict));
-  }
-  // Finally, store the result in the pref service.
-  pref_service_->Set(prefs::kRemoteSuggestionCategories, list);
-}
-
-RemoteSuggestionsProvider::CategoryContent::CategoryContent(
-    const CategoryInfo& info)
-    : info(info) {}
-
-RemoteSuggestionsProvider::CategoryContent::CategoryContent(CategoryContent&&) =
-    default;
-
-RemoteSuggestionsProvider::CategoryContent::~CategoryContent() = default;
-
-RemoteSuggestionsProvider::CategoryContent&
-RemoteSuggestionsProvider::CategoryContent::operator=(CategoryContent&&) =
-    default;
-
 }  // namespace ntp_snippets
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider.h b/components/ntp_snippets/remote/remote_suggestions_provider.h
index e743b42..8b818b5 100644
--- a/components/ntp_snippets/remote/remote_suggestions_provider.h
+++ b/components/ntp_snippets/remote/remote_suggestions_provider.h
@@ -5,448 +5,56 @@
 #ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_H_
 #define COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_H_
 
-#include <cstddef>
-#include <deque>
-#include <map>
 #include <memory>
-#include <set>
-#include <string>
-#include <utility>
-#include <vector>
 
 #include "base/callback_forward.h"
-#include "base/gtest_prod_util.h"
 #include "base/macros.h"
-#include "base/time/clock.h"
-#include "base/time/time.h"
-#include "components/image_fetcher/image_fetcher_delegate.h"
-#include "components/ntp_snippets/category.h"
-#include "components/ntp_snippets/category_status.h"
-#include "components/ntp_snippets/content_suggestion.h"
 #include "components/ntp_snippets/content_suggestions_provider.h"
-#include "components/ntp_snippets/remote/ntp_snippet.h"
-#include "components/ntp_snippets/remote/ntp_snippets_fetcher.h"
-#include "components/ntp_snippets/remote/ntp_snippets_request_params.h"
-#include "components/ntp_snippets/remote/ntp_snippets_scheduler.h"
-#include "components/ntp_snippets/remote/remote_suggestions_status_service.h"
-#include "components/ntp_snippets/remote/request_throttler.h"
-
-class PrefRegistrySimple;
-class PrefService;
-
-namespace gfx {
-class Image;
-}  // namespace gfx
-
-namespace image_fetcher {
-class ImageDecoder;
-class ImageFetcher;
-}  // namespace image_fetcher
 
 namespace ntp_snippets {
 
-class RemoteSuggestionsDatabase;
-class CategoryRanker;
-class UserClassifier;
-
-// CachedImageFetcher takes care of fetching images from the network and caching
-// them in the database.
-// TODO(tschumann): Move into a separate library and inject the
-// CachedImageFetcher into the RemoteSuggestionsProvider. This allows us to get
-// rid of exposing this member for testing and lets us test the caching logic
-// separately.
-class CachedImageFetcher : public image_fetcher::ImageFetcherDelegate {
- public:
-  // |pref_service| and |database| need to outlive the created image fetcher
-  // instance.
-  CachedImageFetcher(std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
-                     std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
-                     PrefService* pref_service,
-                     RemoteSuggestionsDatabase* database);
-  ~CachedImageFetcher() override;
-
-  // Fetches the image for a suggestion. The fetcher will first issue a lookup
-  // to the underlying cache with a fallback to the network.
-  void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
-                            const GURL& image_url,
-                            const ImageFetchedCallback& callback);
-
- private:
-  // image_fetcher::ImageFetcherDelegate implementation.
-  void OnImageDataFetched(const std::string& id_within_category,
-                          const std::string& image_data) override;
-  void OnImageDecodingDone(const ImageFetchedCallback& callback,
-                           const std::string& id_within_category,
-                           const gfx::Image& image);
-  void OnSnippetImageFetchedFromDatabase(
-      const ImageFetchedCallback& callback,
-      const ContentSuggestion::ID& suggestion_id,
-      const GURL& image_url,
-      // SnippetImageCallback requires nonconst reference
-      std::string data);
-  void OnSnippetImageDecodedFromDatabase(
-      const ImageFetchedCallback& callback,
-      const ContentSuggestion::ID& suggestion_id,
-      const GURL& url,
-      const gfx::Image& image);
-  void FetchSnippetImageFromNetwork(const ContentSuggestion::ID& suggestion_id,
-                                    const GURL& url,
-                                    const ImageFetchedCallback& callback);
-
-  std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher_;
-  std::unique_ptr<image_fetcher::ImageDecoder> image_decoder_;
-  RemoteSuggestionsDatabase* database_;
-  // Request throttler for limiting requests to thumbnail images.
-  RequestThrottler thumbnail_requests_throttler_;
-
-  DISALLOW_COPY_AND_ASSIGN(CachedImageFetcher);
-};
+class NTPSnippetsFetcher;
 
 // Retrieves fresh content data (articles) from the server, stores them and
 // provides them as content suggestions.
-// This class is final because it does things in its constructor which make it
-// unsafe to derive from it.
-// TODO(treib): Introduce two-phase initialization and make the class not final?
-class RemoteSuggestionsProvider final : public ContentSuggestionsProvider {
+class RemoteSuggestionsProvider : public ContentSuggestionsProvider {
  public:
-  // |application_language_code| should be a ISO 639-1 compliant string, e.g.
-  // 'en' or 'en-US'. Note that this code should only specify the language, not
-  // the locale, so 'en_US' (English language with US locale) and 'en-GB_US'
-  // (British English person in the US) are not language codes.
-  RemoteSuggestionsProvider(
-      Observer* observer,
-      PrefService* pref_service,
-      const std::string& application_language_code,
-      CategoryRanker* category_ranker,
-      const UserClassifier* user_classifier,
-      NTPSnippetsScheduler* scheduler,
-      std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher,
-      std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
-      std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
-      std::unique_ptr<RemoteSuggestionsDatabase> database,
-      std::unique_ptr<RemoteSuggestionsStatusService> status_service);
+  // TODO(jkrcal): Would be nice to get rid of this another level of statuses.
+  // Maybe possible while refactoring the RemoteSuggestionsStatusService? (and
+  // letting it notify both the SchedulingRemoteSuggestionsProvider and
+  // RemoteSuggestionsProviderImpl or just the scheduling one).
+  enum class ProviderStatus { ACTIVE, INACTIVE };
+  using ProviderStatusCallback =
+      base::RepeatingCallback<void(ProviderStatus status)>;
+
+  // Callback to notify with the result of a fetch.
+  // TODO(jkrcal): Change to OnceCallback? A OnceCallback does only have a
+  // move-constructor which seems problematic for google mock.
+  using FetchStatusCallback = base::Callback<void(Status status_code)>;
 
   ~RemoteSuggestionsProvider() override;
 
-  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+  // Set a callback to be notified whenever the status of the provider changes.
+  // The initial change is also notified (switching from an initial undecided
+  // status). If the callback is set after the first change, it is called back
+  // immediately.
+  virtual void SetProviderStatusCallback(
+      std::unique_ptr<ProviderStatusCallback> callback) = 0;
 
-  // Returns whether the service is ready. While this is false, the list of
-  // snippets will be empty, and all modifications to it (fetch, dismiss, etc)
-  // will be ignored.
-  bool ready() const { return state_ == State::READY; }
+  // Fetches snippets from the server for all remote categories and replaces old
+  // snippets by the new ones. The request to the server is performed as an
+  // background request. Background requests are used for actions not triggered
+  // by the user and have lower priority on the server. After the fetch
+  // finished, the provided |callback| will be triggered with the status of the
+  // fetch (unless not nullptr).
+  virtual void RefetchInTheBackground(
+      std::unique_ptr<FetchStatusCallback> callback) = 0;
 
-  // Returns whether the service is initialized. While this is false, some
-  // calls may trigger DCHECKs.
-  bool initialized() const { return ready() || state_ == State::DISABLED; }
+  virtual const NTPSnippetsFetcher* snippets_fetcher_for_testing_and_debugging()
+      const = 0;
 
-  // Fetchs content suggestions from the Content Suggestions server for all
-  // remote categories in the background.
-  void FetchSnippetsInTheBackground();
-
-  // Fetchs content suggestions from the Content Suggestions server for all
-  // remote categories. The request to the server is performed as an interactive
-  // request. Interactive requests are used for actions triggered by the user
-  // and request lower latency processing.
-  void FetchSnippetsForAllCategories();
-
-  // Only used in tests and for debugging in snippets-internal/.
-  // TODO(fhorschig): Remove this getter when there is an interface for the
-  // fetcher that allows better mocks.
-  const NTPSnippetsFetcher* snippets_fetcher() const {
-    return snippets_fetcher_.get();
-  }
-
-  // (Re)schedules the periodic fetching of snippets. If |force| is true, the
-  // tasks will be re-scheduled even if they already exist and have the correct
-  // periods.
-  void RescheduleFetching(bool force);
-
-  // ContentSuggestionsProvider implementation
-  CategoryStatus GetCategoryStatus(Category category) override;
-  CategoryInfo GetCategoryInfo(Category category) override;
-  void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
-  void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
-                            const ImageFetchedCallback& callback) override;
-  void Fetch(const Category& category,
-             const std::set<std::string>& known_suggestion_ids,
-             const FetchDoneCallback& callback) override;
-  void ClearHistory(
-      base::Time begin,
-      base::Time end,
-      const base::Callback<bool(const GURL& url)>& filter) override;
-  void ClearCachedSuggestions(Category category) override;
-  void OnSignInStateChanged() override;
-  void GetDismissedSuggestionsForDebugging(
-      Category category,
-      const DismissedSuggestionsCallback& callback) override;
-  void ClearDismissedSuggestionsForDebugging(Category category) override;
-
-  // Returns the maximum number of snippets that will be shown at once.
-  static int GetMaxSnippetCountForTesting();
-
-  // Available snippets, only for unit tests.
-  // TODO(treib): Get rid of this. Tests should use a fake observer instead.
-  const NTPSnippet::PtrVector& GetSnippetsForTesting(Category category) const {
-    return category_contents_.find(category)->second.snippets;
-  }
-
-  // Dismissed snippets, only for unit tests.
-  const NTPSnippet::PtrVector& GetDismissedSnippetsForTesting(
-      Category category) const {
-    return category_contents_.find(category)->second.dismissed;
-  }
-
-  // Overrides internal clock for testing purposes.
-  void SetClockForTesting(std::unique_ptr<base::Clock> clock) {
-    clock_ = std::move(clock);
-  }
-
-  // TODO(tschumann): remove this method as soon as we inject the fetcher into
-  // the constructor.
-  CachedImageFetcher& GetImageFetcherForTesting() { return image_fetcher_; }
-
- private:
-  friend class RemoteSuggestionsProviderTest;
-
-  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest,
-                           DontNotifyIfNotAvailable);
-  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest,
-                           RemoveExpiredDismissedContent);
-  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest,
-                           RescheduleOnStateChange);
-  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest, StatusChanges);
-  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest,
-                           SuggestionsFetchedOnSignInAndSignOut);
-
-  // Possible state transitions:
-  //       NOT_INITED --------+
-  //       /       \          |
-  //      v         v         |
-  //   READY <--> DISABLED    |
-  //       \       /          |
-  //        v     v           |
-  //     ERROR_OCCURRED <-----+
-  enum class State {
-    // The service has just been created. Can change to states:
-    // - DISABLED: After the database is done loading,
-    //             GetStateForDependenciesStatus can identify the next state to
-    //             be DISABLED.
-    // - READY: if GetStateForDependenciesStatus returns it, after the database
-    //          is done loading.
-    // - ERROR_OCCURRED: when an unrecoverable error occurred.
-    NOT_INITED,
-
-    // The service registered observers, timers, etc. and is ready to answer to
-    // queries, fetch snippets... Can change to states:
-    // - DISABLED: when the global Chrome state changes, for example after
-    //             |OnStateChanged| is called and sync is disabled.
-    // - ERROR_OCCURRED: when an unrecoverable error occurred.
-    READY,
-
-    // The service is disabled and unregistered the related resources.
-    // Can change to states:
-    // - READY: when the global Chrome state changes, for example after
-    //          |OnStateChanged| is called and sync is enabled.
-    // - ERROR_OCCURRED: when an unrecoverable error occurred.
-    DISABLED,
-
-    // The service or one of its dependencies encountered an unrecoverable error
-    // and the service can't be used anymore.
-    ERROR_OCCURRED,
-
-    COUNT
-  };
-
-  struct CategoryContent {
-    // The current status of the category.
-    CategoryStatus status = CategoryStatus::INITIALIZING;
-
-    // The additional information about a category.
-    CategoryInfo info;
-
-    // True iff the server returned results in this category in the last fetch.
-    // We never remove categories that the server still provides, but if the
-    // server stops providing a category, we won't yet report it as NOT_PROVIDED
-    // while we still have non-expired snippets in it.
-    bool included_in_last_server_response = true;
-
-    // All currently active suggestions (excl. the dismissed ones).
-    NTPSnippet::PtrVector snippets;
-
-    // All previous suggestions that we keep around in memory because they can
-    // be on some open NTP. We do not persist this list so that on a new start
-    // of Chrome, this is empty.
-    // |archived| is a FIFO buffer with a maximum length.
-    std::deque<std::unique_ptr<NTPSnippet>> archived;
-
-    // Suggestions that the user dismissed. We keep these around until they
-    // expire so we won't re-add them to |snippets| on the next fetch.
-    NTPSnippet::PtrVector dismissed;
-
-    // Returns a non-dismissed snippet with the given |id_within_category|, or
-    // null if none exist.
-    const NTPSnippet* FindSnippet(const std::string& id_within_category) const;
-
-    explicit CategoryContent(const CategoryInfo& info);
-    CategoryContent(CategoryContent&&);
-    ~CategoryContent();
-    CategoryContent& operator=(CategoryContent&&);
-  };
-
-  // Fetches snippets from the server and replaces old snippets by the new ones.
-  // Requests can be marked more important by setting |interactive_request| to
-  // true (such request might circumvent the daily quota for requests, etc.)
-  // Useful for requests triggered by the user.
-  void FetchSnippets(bool interactive_request);
-
-  // Returns the URL of the image of a snippet if it is among the current or
-  // among the archived snippets in the matching category. Returns an empty URL
-  // otherwise.
-  GURL FindSnippetImageUrl(const ContentSuggestion::ID& suggestion_id) const;
-
-  // Callbacks for the RemoteSuggestionsDatabase.
-  void OnDatabaseLoaded(NTPSnippet::PtrVector snippets);
-  void OnDatabaseError();
-
-  // Callback for fetch-more requests with the NTPSnippetsFetcher.
-  void OnFetchMoreFinished(
-      const FetchDoneCallback& fetching_callback,
-      Status status,
-      NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories);
-
-  // Callback for regular fetch requests with the NTPSnippetsFetcher.
-  void OnFetchFinished(
-      bool interactive_request,
-      Status status,
-      NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories);
-
-  // Moves all snippets from |to_archive| into the archive of the |content|.
-  // Clears |to_archive|. As the archive is a FIFO buffer of limited size, this
-  // function will also delete images from the database in case the associated
-  // snippet gets evicted from the archive.
-  void ArchiveSnippets(CategoryContent* content,
-                       NTPSnippet::PtrVector* to_archive);
-
-  // Sanitizes newly fetched snippets -- e.g. adding missing dates and filtering
-  // out incomplete results or dismissed snippets (indicated by |dismissed|).
-  void SanitizeReceivedSnippets(const NTPSnippet::PtrVector& dismissed,
-                                NTPSnippet::PtrVector* snippets);
-
-  // Adds newly available suggestions to |content|.
-  void IntegrateSnippets(CategoryContent* content,
-                         NTPSnippet::PtrVector new_snippets);
-
-  // Dismisses a snippet within a given category content.
-  // Note that this modifies the snippet datastructures of |content|
-  // invalidating iterators.
-  void DismissSuggestionFromCategoryContent(
-      CategoryContent* content,
-      const std::string& id_within_category);
-
-  // Removes expired dismissed snippets from the service and the database.
-  void ClearExpiredDismissedSnippets();
-
-  // Removes images from the DB that are not referenced from any known snippet.
-  // Needs to iterate the whole snippet database -- so do it often enough to
-  // keep it small but not too often as it still iterates over the file system.
-  void ClearOrphanedImages();
-
-  // Clears all stored snippets and updates the observer.
-  void NukeAllSnippets();
-
-  // Completes the initialization phase of the service, registering the last
-  // observers. This is done after construction, once the database is loaded.
-  void FinishInitialization();
-
-  // Triggers a state transition depending on the provided status. This method
-  // is called when a change is detected by |status_service_|.
-  void OnStatusChanged(RemoteSuggestionsStatus old_status,
-                       RemoteSuggestionsStatus new_status);
-
-  // Verifies state transitions (see |State|'s documentation) and applies them.
-  // Also updates the provider status. Does nothing except updating the provider
-  // status if called with the current state.
-  void EnterState(State state);
-
-  // Enables the service. Do not call directly, use |EnterState| instead.
-  void EnterStateReady();
-
-  // Disables the service. Do not call directly, use |EnterState| instead.
-  void EnterStateDisabled();
-
-  // Disables the service permanently because an unrecoverable error occurred.
-  // Do not call directly, use |EnterState| instead.
-  void EnterStateError();
-
-  // Converts the cached snippets in the given |category| to content suggestions
-  // and notifies the observer.
-  void NotifyNewSuggestions(Category category, const CategoryContent& content);
-
-  // Updates the internal status for |category| to |category_status_| and
-  // notifies the content suggestions observer if it changed.
-  void UpdateCategoryStatus(Category category, CategoryStatus status);
-  // Calls UpdateCategoryStatus() for all provided categories.
-  void UpdateAllCategoryStatus(CategoryStatus status);
-
-  // Updates the category info for |category|. If a corresponding
-  // CategoryContent object does not exist, it will be created.
-  // Returns the existing or newly created object.
-  CategoryContent* UpdateCategoryInfo(Category category,
-                                      const CategoryInfo& info);
-
-  void RestoreCategoriesFromPrefs();
-  void StoreCategoriesToPrefs();
-
-  NTPSnippetsRequestParams BuildFetchParams() const;
-
-  void MarkEmptyCategoriesAsLoading();
-
-  State state_;
-
-  PrefService* pref_service_;
-
-  const Category articles_category_;
-
-  std::map<Category, CategoryContent, Category::CompareByID> category_contents_;
-
-  // The ISO 639-1 code of the language used by the application.
-  const std::string application_language_code_;
-
-  // Ranker that orders the categories. Not owned.
-  CategoryRanker* category_ranker_;
-
-  // Classifier that tells us how active the user is. Not owned.
-  const UserClassifier* user_classifier_;
-
-  // Scheduler for fetching snippets. Not owned.
-  NTPSnippetsScheduler* scheduler_;
-
-  // The snippets fetcher.
-  std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher_;
-
-  // The database for persisting snippets.
-  std::unique_ptr<RemoteSuggestionsDatabase> database_;
-  base::TimeTicks database_load_start_;
-
-  // The image fetcher.
-  CachedImageFetcher image_fetcher_;
-
-  // The service that provides events and data about the signin and sync state.
-  std::unique_ptr<RemoteSuggestionsStatusService> status_service_;
-
-  // Set to true if FetchSnippets is called while the service isn't ready.
-  // The fetch will be executed once the service enters the READY state.
-  bool fetch_when_ready_;
-
-  // Set to true if NukeAllSnippets is called while the service isn't ready.
-  // The nuke will be executed once the service finishes initialization or
-  // enters the READY state.
-  bool nuke_when_initialized_;
-
-  // A clock for getting the time. This allows to inject a clock in tests.
-  std::unique_ptr<base::Clock> clock_;
-
-  DISALLOW_COPY_AND_ASSIGN(RemoteSuggestionsProvider);
+ protected:
+  RemoteSuggestionsProvider(Observer* observer);
 };
 
 }  // namespace ntp_snippets
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc b/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
new file mode 100644
index 0000000..7515c30
--- /dev/null
+++ b/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
@@ -0,0 +1,1302 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
+
+#include <algorithm>
+#include <iterator>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/location.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/default_clock.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/data_use_measurement/core/data_use_user_data.h"
+#include "components/history/core/browser/history_service.h"
+#include "components/image_fetcher/image_decoder.h"
+#include "components/image_fetcher/image_fetcher.h"
+#include "components/ntp_snippets/category_rankers/category_ranker.h"
+#include "components/ntp_snippets/features.h"
+#include "components/ntp_snippets/pref_names.h"
+#include "components/ntp_snippets/remote/remote_suggestions_database.h"
+#include "components/ntp_snippets/switches.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/variations_associated_data.h"
+#include "grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/image/image.h"
+
+namespace ntp_snippets {
+
+namespace {
+
+// Number of snippets requested to the server. Consider replacing sparse UMA
+// histograms with COUNTS() if this number increases beyond 50.
+const int kMaxSnippetCount = 10;
+
+// Number of archived snippets we keep around in memory.
+const int kMaxArchivedSnippetCount = 200;
+
+// Keys for storing CategoryContent info in prefs.
+const char kCategoryContentId[] = "id";
+const char kCategoryContentTitle[] = "title";
+const char kCategoryContentProvidedByServer[] = "provided_by_server";
+const char kCategoryContentAllowFetchingMore[] = "allow_fetching_more";
+
+// TODO(treib): Remove after M57.
+const char kDeprecatedSnippetHostsPref[] = "ntp_snippets.hosts";
+
+std::unique_ptr<std::vector<std::string>> GetSnippetIDVector(
+    const NTPSnippet::PtrVector& snippets) {
+  auto result = base::MakeUnique<std::vector<std::string>>();
+  for (const auto& snippet : snippets) {
+    result->push_back(snippet->id());
+  }
+  return result;
+}
+
+bool HasIntersection(const std::vector<std::string>& a,
+                     const std::set<std::string>& b) {
+  for (const std::string& item : a) {
+    if (base::ContainsValue(b, item)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void EraseByPrimaryID(NTPSnippet::PtrVector* snippets,
+                      const std::vector<std::string>& ids) {
+  std::set<std::string> ids_lookup(ids.begin(), ids.end());
+  snippets->erase(
+      std::remove_if(snippets->begin(), snippets->end(),
+                     [&ids_lookup](const std::unique_ptr<NTPSnippet>& snippet) {
+                       return base::ContainsValue(ids_lookup, snippet->id());
+                     }),
+      snippets->end());
+}
+
+void EraseMatchingSnippets(NTPSnippet::PtrVector* snippets,
+                           const NTPSnippet::PtrVector& compare_against) {
+  std::set<std::string> compare_against_ids;
+  for (const std::unique_ptr<NTPSnippet>& snippet : compare_against) {
+    const std::vector<std::string>& snippet_ids = snippet->GetAllIDs();
+    compare_against_ids.insert(snippet_ids.begin(), snippet_ids.end());
+  }
+  snippets->erase(
+      std::remove_if(
+          snippets->begin(), snippets->end(),
+          [&compare_against_ids](const std::unique_ptr<NTPSnippet>& snippet) {
+            return HasIntersection(snippet->GetAllIDs(), compare_against_ids);
+          }),
+      snippets->end());
+}
+
+void RemoveNullPointers(NTPSnippet::PtrVector* snippets) {
+  snippets->erase(
+      std::remove_if(
+          snippets->begin(), snippets->end(),
+          [](const std::unique_ptr<NTPSnippet>& snippet) { return !snippet; }),
+      snippets->end());
+}
+
+void RemoveIncompleteSnippets(NTPSnippet::PtrVector* snippets) {
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kAddIncompleteSnippets)) {
+    return;
+  }
+  int num_snippets = snippets->size();
+  // Remove snippets that do not have all the info we need to display it to
+  // the user.
+  snippets->erase(
+      std::remove_if(snippets->begin(), snippets->end(),
+                     [](const std::unique_ptr<NTPSnippet>& snippet) {
+                       return !snippet->is_complete();
+                     }),
+      snippets->end());
+  int num_snippets_removed = num_snippets - snippets->size();
+  UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch",
+                        num_snippets_removed > 0);
+  if (num_snippets_removed > 0) {
+    UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets",
+                                num_snippets_removed);
+  }
+}
+
+std::vector<ContentSuggestion> ConvertToContentSuggestions(
+    Category category,
+    const NTPSnippet::PtrVector& snippets) {
+  std::vector<ContentSuggestion> result;
+  for (const std::unique_ptr<NTPSnippet>& snippet : snippets) {
+    // TODO(sfiera): if a snippet is not going to be displayed, move it
+    // directly to content.dismissed on fetch. Otherwise, we might prune
+    // other snippets to get down to kMaxSnippetCount, only to hide one of the
+    // incomplete ones we kept.
+    if (!snippet->is_complete()) {
+      continue;
+    }
+    GURL url = snippet->url();
+    if (base::FeatureList::IsEnabled(kPreferAmpUrlsFeature) &&
+        !snippet->amp_url().is_empty()) {
+      url = snippet->amp_url();
+    }
+    ContentSuggestion suggestion(category, snippet->id(), url);
+    suggestion.set_title(base::UTF8ToUTF16(snippet->title()));
+    suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet()));
+    suggestion.set_publish_date(snippet->publish_date());
+    suggestion.set_publisher_name(base::UTF8ToUTF16(snippet->publisher_name()));
+    suggestion.set_score(snippet->score());
+    result.emplace_back(std::move(suggestion));
+  }
+  return result;
+}
+
+void CallWithEmptyResults(const FetchDoneCallback& callback,
+                          const Status& status) {
+  if (callback.is_null()) {
+    return;
+  }
+  callback.Run(status, std::vector<ContentSuggestion>());
+}
+
+}  // namespace
+
+CachedImageFetcher::CachedImageFetcher(
+    std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
+    std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
+    PrefService* pref_service,
+    RemoteSuggestionsDatabase* database)
+    : image_fetcher_(std::move(image_fetcher)),
+      image_decoder_(std::move(image_decoder)),
+      database_(database),
+      thumbnail_requests_throttler_(
+          pref_service,
+          RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) {
+  // |image_fetcher_| can be null in tests.
+  if (image_fetcher_) {
+    image_fetcher_->SetImageFetcherDelegate(this);
+    image_fetcher_->SetDataUseServiceName(
+        data_use_measurement::DataUseUserData::NTP_SNIPPETS);
+  }
+}
+
+CachedImageFetcher::~CachedImageFetcher() {}
+
+void CachedImageFetcher::FetchSuggestionImage(
+    const ContentSuggestion::ID& suggestion_id,
+    const GURL& url,
+    const ImageFetchedCallback& callback) {
+  database_->LoadImage(
+      suggestion_id.id_within_category(),
+      base::Bind(&CachedImageFetcher::OnSnippetImageFetchedFromDatabase,
+                 base::Unretained(this), callback, suggestion_id, url));
+}
+
+// This function gets only called for caching the image data received from the
+// network. The actual decoding is done in OnSnippetImageDecodedFromDatabase().
+void CachedImageFetcher::OnImageDataFetched(
+    const std::string& id_within_category,
+    const std::string& image_data) {
+  if (image_data.empty()) {
+    return;
+  }
+  database_->SaveImage(id_within_category, image_data);
+}
+
+void CachedImageFetcher::OnImageDecodingDone(
+    const ImageFetchedCallback& callback,
+    const std::string& id_within_category,
+    const gfx::Image& image) {
+  callback.Run(image);
+}
+
+void CachedImageFetcher::OnSnippetImageFetchedFromDatabase(
+    const ImageFetchedCallback& callback,
+    const ContentSuggestion::ID& suggestion_id,
+    const GURL& url,
+    std::string data) {  // SnippetImageCallback requires nonconst reference.
+  // |image_decoder_| is null in tests.
+  if (image_decoder_ && !data.empty()) {
+    image_decoder_->DecodeImage(
+        data, base::Bind(&CachedImageFetcher::OnSnippetImageDecodedFromDatabase,
+                         base::Unretained(this), callback, suggestion_id, url));
+    return;
+  }
+  // Fetching from the DB failed; start a network fetch.
+  FetchSnippetImageFromNetwork(suggestion_id, url, callback);
+}
+
+void CachedImageFetcher::OnSnippetImageDecodedFromDatabase(
+    const ImageFetchedCallback& callback,
+    const ContentSuggestion::ID& suggestion_id,
+    const GURL& url,
+    const gfx::Image& image) {
+  if (!image.IsEmpty()) {
+    callback.Run(image);
+    return;
+  }
+  // If decoding the image failed, delete the DB entry.
+  database_->DeleteImage(suggestion_id.id_within_category());
+  FetchSnippetImageFromNetwork(suggestion_id, url, callback);
+}
+
+void CachedImageFetcher::FetchSnippetImageFromNetwork(
+    const ContentSuggestion::ID& suggestion_id,
+    const GURL& url,
+    const ImageFetchedCallback& callback) {
+  if (url.is_empty() ||
+      !thumbnail_requests_throttler_.DemandQuotaForRequest(
+          /*interactive_request=*/true)) {
+    // Return an empty image. Directly, this is never synchronous with the
+    // original FetchSuggestionImage() call - an asynchronous database query has
+    // happened in the meantime.
+    callback.Run(gfx::Image());
+    return;
+  }
+
+  image_fetcher_->StartOrQueueNetworkRequest(
+      suggestion_id.id_within_category(), url,
+      base::Bind(&CachedImageFetcher::OnImageDecodingDone,
+                 base::Unretained(this), callback));
+}
+
+RemoteSuggestionsProviderImpl::RemoteSuggestionsProviderImpl(
+    Observer* observer,
+    PrefService* pref_service,
+    const std::string& application_language_code,
+    CategoryRanker* category_ranker,
+    std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher,
+    std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
+    std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
+    std::unique_ptr<RemoteSuggestionsDatabase> database,
+    std::unique_ptr<RemoteSuggestionsStatusService> status_service)
+    : RemoteSuggestionsProvider(observer),
+      state_(State::NOT_INITED),
+      pref_service_(pref_service),
+      articles_category_(
+          Category::FromKnownCategory(KnownCategories::ARTICLES)),
+      application_language_code_(application_language_code),
+      category_ranker_(category_ranker),
+      snippets_fetcher_(std::move(snippets_fetcher)),
+      database_(std::move(database)),
+      image_fetcher_(std::move(image_fetcher),
+                     std::move(image_decoder),
+                     pref_service,
+                     database_.get()),
+      status_service_(std::move(status_service)),
+      fetch_when_ready_(false),
+      fetch_when_ready_interactive_(false),
+      fetch_when_ready_callback_(nullptr),
+      provider_status_callback_(nullptr),
+      nuke_when_initialized_(false),
+      clock_(base::MakeUnique<base::DefaultClock>()) {
+  pref_service_->ClearPref(kDeprecatedSnippetHostsPref);
+
+  RestoreCategoriesFromPrefs();
+  // The articles category always exists. Add it if we didn't get it from prefs.
+  // TODO(treib): Rethink this.
+  category_contents_.insert(
+      std::make_pair(articles_category_,
+                     CategoryContent(BuildArticleCategoryInfo(base::nullopt))));
+  // Tell the observer about all the categories.
+  for (const auto& entry : category_contents_) {
+    observer->OnCategoryStatusChanged(this, entry.first, entry.second.status);
+  }
+
+  if (database_->IsErrorState()) {
+    EnterState(State::ERROR_OCCURRED);
+    UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR);
+    return;
+  }
+
+  database_->SetErrorCallback(base::Bind(
+      &RemoteSuggestionsProviderImpl::OnDatabaseError, base::Unretained(this)));
+
+  // We transition to other states while finalizing the initialization, when the
+  // database is done loading.
+  database_load_start_ = base::TimeTicks::Now();
+  database_->LoadSnippets(
+      base::Bind(&RemoteSuggestionsProviderImpl::OnDatabaseLoaded,
+                 base::Unretained(this)));
+}
+
+RemoteSuggestionsProviderImpl::~RemoteSuggestionsProviderImpl() = default;
+
+// static
+void RemoteSuggestionsProviderImpl::RegisterProfilePrefs(
+    PrefRegistrySimple* registry) {
+  // TODO(treib): Remove after M57.
+  registry->RegisterListPref(kDeprecatedSnippetHostsPref);
+  registry->RegisterListPref(prefs::kRemoteSuggestionCategories);
+  registry->RegisterInt64Pref(prefs::kLastSuccessfulBackgroundFetchTime, 0);
+
+  RemoteSuggestionsStatusService::RegisterProfilePrefs(registry);
+}
+
+void RemoteSuggestionsProviderImpl::SetProviderStatusCallback(
+    std::unique_ptr<ProviderStatusCallback> callback) {
+  provider_status_callback_ = std::move(callback);
+  // Call the observer right away if we've reached any final state.
+  NotifyStateChanged();
+}
+
+void RemoteSuggestionsProviderImpl::ReloadSuggestions() {
+  FetchSnippets(/*interactive_request=*/true,
+                /*callback=*/nullptr);
+}
+
+void RemoteSuggestionsProviderImpl::RefetchInTheBackground(
+    std::unique_ptr<FetchStatusCallback> callback) {
+  FetchSnippets(/*interactive_request=*/false, std::move(callback));
+}
+
+const NTPSnippetsFetcher*
+RemoteSuggestionsProviderImpl::snippets_fetcher_for_testing_and_debugging()
+    const {
+  return snippets_fetcher_.get();
+}
+
+void RemoteSuggestionsProviderImpl::FetchSnippets(
+    bool interactive_request,
+    std::unique_ptr<FetchStatusCallback> callback) {
+  if (!ready()) {
+    fetch_when_ready_ = true;
+    fetch_when_ready_interactive_ = interactive_request;
+    fetch_when_ready_callback_ = std::move(callback);
+    return;
+  }
+
+  MarkEmptyCategoriesAsLoading();
+
+  NTPSnippetsRequestParams params = BuildFetchParams();
+  params.interactive_request = interactive_request;
+  snippets_fetcher_->FetchSnippets(
+      params, base::BindOnce(&RemoteSuggestionsProviderImpl::OnFetchFinished,
+                             base::Unretained(this), std::move(callback),
+                             interactive_request));
+}
+
+void RemoteSuggestionsProviderImpl::Fetch(
+    const Category& category,
+    const std::set<std::string>& known_suggestion_ids,
+    const FetchDoneCallback& callback) {
+  if (!ready()) {
+    CallWithEmptyResults(callback,
+                         Status(StatusCode::TEMPORARY_ERROR,
+                                "RemoteSuggestionsProvider is not ready!"));
+    return;
+  }
+  NTPSnippetsRequestParams params = BuildFetchParams();
+  params.excluded_ids.insert(known_suggestion_ids.begin(),
+                             known_suggestion_ids.end());
+  params.interactive_request = true;
+  params.exclusive_category = category;
+
+  snippets_fetcher_->FetchSnippets(
+      params,
+      base::BindOnce(&RemoteSuggestionsProviderImpl::OnFetchMoreFinished,
+                     base::Unretained(this), callback));
+}
+
+// Builds default fetcher params.
+NTPSnippetsRequestParams RemoteSuggestionsProviderImpl::BuildFetchParams()
+    const {
+  NTPSnippetsRequestParams result;
+  result.language_code = application_language_code_;
+  result.count_to_fetch = kMaxSnippetCount;
+  for (const auto& map_entry : category_contents_) {
+    const CategoryContent& content = map_entry.second;
+    for (const auto& dismissed_snippet : content.dismissed) {
+      result.excluded_ids.insert(dismissed_snippet->id());
+    }
+  }
+  return result;
+}
+
+void RemoteSuggestionsProviderImpl::MarkEmptyCategoriesAsLoading() {
+  for (const auto& item : category_contents_) {
+    Category category = item.first;
+    const CategoryContent& content = item.second;
+    if (content.snippets.empty()) {
+      UpdateCategoryStatus(category, CategoryStatus::AVAILABLE_LOADING);
+    }
+  }
+}
+
+CategoryStatus RemoteSuggestionsProviderImpl::GetCategoryStatus(
+    Category category) {
+  auto content_it = category_contents_.find(category);
+  DCHECK(content_it != category_contents_.end());
+  return content_it->second.status;
+}
+
+CategoryInfo RemoteSuggestionsProviderImpl::GetCategoryInfo(Category category) {
+  auto content_it = category_contents_.find(category);
+  DCHECK(content_it != category_contents_.end());
+  return content_it->second.info;
+}
+
+void RemoteSuggestionsProviderImpl::DismissSuggestion(
+    const ContentSuggestion::ID& suggestion_id) {
+  if (!ready()) {
+    return;
+  }
+
+  auto content_it = category_contents_.find(suggestion_id.category());
+  DCHECK(content_it != category_contents_.end());
+  CategoryContent* content = &content_it->second;
+  DismissSuggestionFromCategoryContent(content,
+                                       suggestion_id.id_within_category());
+}
+
+void RemoteSuggestionsProviderImpl::ClearHistory(
+    base::Time begin,
+    base::Time end,
+    const base::Callback<bool(const GURL& url)>& filter) {
+  // Both time range and the filter are ignored and all suggestions are removed,
+  // because it is not known which history entries were used for the suggestions
+  // personalization.
+  if (!ready()) {
+    nuke_when_initialized_ = true;
+  } else {
+    NukeAllSnippets();
+  }
+}
+
+void RemoteSuggestionsProviderImpl::ClearCachedSuggestions(Category category) {
+  if (!initialized()) {
+    return;
+  }
+
+  auto content_it = category_contents_.find(category);
+  if (content_it == category_contents_.end()) {
+    return;
+  }
+  CategoryContent* content = &content_it->second;
+  if (content->snippets.empty()) {
+    return;
+  }
+
+  database_->DeleteSnippets(GetSnippetIDVector(content->snippets));
+  database_->DeleteImages(GetSnippetIDVector(content->snippets));
+  content->snippets.clear();
+
+  if (IsCategoryStatusAvailable(content->status)) {
+    NotifyNewSuggestions(category, *content);
+  }
+}
+
+void RemoteSuggestionsProviderImpl::OnSignInStateChanged() {
+  // Make sure the status service is registered and we already initialised its
+  // start state.
+  if (!initialized()) {
+    return;
+  }
+
+  status_service_->OnSignInStateChanged();
+}
+
+void RemoteSuggestionsProviderImpl::GetDismissedSuggestionsForDebugging(
+    Category category,
+    const DismissedSuggestionsCallback& callback) {
+  auto content_it = category_contents_.find(category);
+  DCHECK(content_it != category_contents_.end());
+  callback.Run(
+      ConvertToContentSuggestions(category, content_it->second.dismissed));
+}
+
+void RemoteSuggestionsProviderImpl::ClearDismissedSuggestionsForDebugging(
+    Category category) {
+  auto content_it = category_contents_.find(category);
+  DCHECK(content_it != category_contents_.end());
+  CategoryContent* content = &content_it->second;
+
+  if (!initialized()) {
+    return;
+  }
+
+  if (content->dismissed.empty()) {
+    return;
+  }
+
+  database_->DeleteSnippets(GetSnippetIDVector(content->dismissed));
+  // The image got already deleted when the suggestion was dismissed.
+
+  content->dismissed.clear();
+}
+
+// static
+int RemoteSuggestionsProviderImpl::GetMaxSnippetCountForTesting() {
+  return kMaxSnippetCount;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private methods
+
+GURL RemoteSuggestionsProviderImpl::FindSnippetImageUrl(
+    const ContentSuggestion::ID& suggestion_id) const {
+  DCHECK(base::ContainsKey(category_contents_, suggestion_id.category()));
+
+  const CategoryContent& content =
+      category_contents_.at(suggestion_id.category());
+  const NTPSnippet* snippet =
+      content.FindSnippet(suggestion_id.id_within_category());
+  if (!snippet) {
+    return GURL();
+  }
+  return snippet->salient_image_url();
+}
+
+void RemoteSuggestionsProviderImpl::OnDatabaseLoaded(
+    NTPSnippet::PtrVector snippets) {
+  if (state_ == State::ERROR_OCCURRED) {
+    return;
+  }
+  DCHECK(state_ == State::NOT_INITED);
+  DCHECK(base::ContainsKey(category_contents_, articles_category_));
+
+  base::TimeDelta database_load_time =
+      base::TimeTicks::Now() - database_load_start_;
+  UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Snippets.DatabaseLoadTime",
+                             database_load_time);
+
+  NTPSnippet::PtrVector to_delete;
+  for (std::unique_ptr<NTPSnippet>& snippet : snippets) {
+    Category snippet_category =
+        Category::FromRemoteCategory(snippet->remote_category_id());
+    auto content_it = category_contents_.find(snippet_category);
+    // We should already know about the category.
+    if (content_it == category_contents_.end()) {
+      DLOG(WARNING) << "Loaded a suggestion for unknown category "
+                    << snippet_category << " from the DB; deleting";
+      to_delete.emplace_back(std::move(snippet));
+      continue;
+    }
+    CategoryContent* content = &content_it->second;
+    if (snippet->is_dismissed()) {
+      content->dismissed.emplace_back(std::move(snippet));
+    } else {
+      content->snippets.emplace_back(std::move(snippet));
+    }
+  }
+  if (!to_delete.empty()) {
+    database_->DeleteSnippets(GetSnippetIDVector(to_delete));
+    database_->DeleteImages(GetSnippetIDVector(to_delete));
+  }
+
+  // Sort the suggestions in each category.
+  // TODO(treib): Persist the actual order in the DB somehow? crbug.com/654409
+  for (auto& entry : category_contents_) {
+    CategoryContent* content = &entry.second;
+    std::sort(content->snippets.begin(), content->snippets.end(),
+              [](const std::unique_ptr<NTPSnippet>& lhs,
+                 const std::unique_ptr<NTPSnippet>& rhs) {
+                return lhs->score() > rhs->score();
+              });
+  }
+
+  // TODO(tschumann): If I move ClearExpiredDismissedSnippets() to the beginning
+  // of the function, it essentially does nothing but tests are still green. Fix
+  // this!
+  ClearExpiredDismissedSnippets();
+  ClearOrphanedImages();
+  FinishInitialization();
+}
+
+void RemoteSuggestionsProviderImpl::OnDatabaseError() {
+  EnterState(State::ERROR_OCCURRED);
+  UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR);
+}
+
+void RemoteSuggestionsProviderImpl::OnFetchMoreFinished(
+    const FetchDoneCallback& fetching_callback,
+    Status status,
+    NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories) {
+  if (!fetched_categories) {
+    DCHECK(!status.IsSuccess());
+    CallWithEmptyResults(fetching_callback, status);
+    return;
+  }
+  if (fetched_categories->size() != 1u) {
+    LOG(DFATAL) << "Requested one exclusive category but received "
+                << fetched_categories->size() << " categories.";
+    CallWithEmptyResults(fetching_callback,
+                         Status(StatusCode::PERMANENT_ERROR,
+                                "RemoteSuggestionsProvider received more "
+                                "categories than requested."));
+    return;
+  }
+  auto& fetched_category = (*fetched_categories)[0];
+  Category category = fetched_category.category;
+  CategoryContent* existing_content =
+      UpdateCategoryInfo(category, fetched_category.info);
+  SanitizeReceivedSnippets(existing_content->dismissed,
+                           &fetched_category.snippets);
+  // We compute the result now before modifying |fetched_category.snippets|.
+  // However, we wait with notifying the caller until the end of the method when
+  // all state is updated.
+  std::vector<ContentSuggestion> result =
+      ConvertToContentSuggestions(category, fetched_category.snippets);
+
+  // Fill up the newly fetched snippets with existing ones, store them, and
+  // notify observers about new data.
+  while (fetched_category.snippets.size() <
+             static_cast<size_t>(kMaxSnippetCount) &&
+         !existing_content->snippets.empty()) {
+    fetched_category.snippets.emplace(
+        fetched_category.snippets.begin(),
+        std::move(existing_content->snippets.back()));
+    existing_content->snippets.pop_back();
+  }
+  std::vector<std::string> to_dismiss =
+      *GetSnippetIDVector(existing_content->snippets);
+  for (const auto& id : to_dismiss) {
+    DismissSuggestionFromCategoryContent(existing_content, id);
+  }
+  DCHECK(existing_content->snippets.empty());
+
+  IntegrateSnippets(existing_content, std::move(fetched_category.snippets));
+
+  // TODO(tschumann): We should properly honor the existing category state,
+  // e.g. to make sure we don't serve results after the sign-out. Revisit this
+  // once the snippets fetcher supports concurrent requests. We can then see if
+  // Nuke should also cancel outstanding requests or we want to check the
+  // status.
+  UpdateCategoryStatus(category, CategoryStatus::AVAILABLE);
+  // Notify callers and observers.
+  fetching_callback.Run(Status::Success(), std::move(result));
+  NotifyNewSuggestions(category, *existing_content);
+}
+
+void RemoteSuggestionsProviderImpl::OnFetchFinished(
+    std::unique_ptr<FetchStatusCallback> callback,
+    bool interactive_request,
+    Status status,
+    NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories) {
+  if (!ready()) {
+    // TODO(tschumann): What happens if this was a user-triggered, interactive
+    // request? Is the UI waiting indefinitely now?
+    return;
+  }
+
+  // Record the fetch time of a successfull background fetch.
+  if (!interactive_request && status.IsSuccess()) {
+    pref_service_->SetInt64(prefs::kLastSuccessfulBackgroundFetchTime,
+                            clock_->Now().ToInternalValue());
+  }
+
+  // Mark all categories as not provided by the server in the latest fetch. The
+  // ones we got will be marked again below.
+  for (auto& item : category_contents_) {
+    CategoryContent* content = &item.second;
+    content->included_in_last_server_response = false;
+  }
+
+  // Clear up expired dismissed snippets before we use them to filter new ones.
+  ClearExpiredDismissedSnippets();
+
+  // If snippets were fetched successfully, update our |category_contents_| from
+  // each category provided by the server.
+  if (fetched_categories) {
+    // TODO(treib): Reorder |category_contents_| to match the order we received
+    // from the server. crbug.com/653816
+    for (NTPSnippetsFetcher::FetchedCategory& fetched_category :
+         *fetched_categories) {
+      // TODO(tschumann): Remove this histogram once we only talk to the content
+      // suggestions cloud backend.
+      if (fetched_category.category == articles_category_) {
+        UMA_HISTOGRAM_SPARSE_SLOWLY(
+            "NewTabPage.Snippets.NumArticlesFetched",
+            std::min(fetched_category.snippets.size(),
+                     static_cast<size_t>(kMaxSnippetCount + 1)));
+      }
+      category_ranker_->AppendCategoryIfNecessary(fetched_category.category);
+      CategoryContent* content =
+          UpdateCategoryInfo(fetched_category.category, fetched_category.info);
+      content->included_in_last_server_response = true;
+      SanitizeReceivedSnippets(content->dismissed, &fetched_category.snippets);
+      IntegrateSnippets(content, std::move(fetched_category.snippets));
+    }
+  }
+
+  // TODO(tschumann): The snippets fetcher needs to signal errors so that we
+  // know why we received no data. If an error occured, none of the following
+  // should take place.
+
+  // We might have gotten new categories (or updated the titles of existing
+  // ones), so update the pref.
+  StoreCategoriesToPrefs();
+
+  for (const auto& item : category_contents_) {
+    Category category = item.first;
+    UpdateCategoryStatus(category, CategoryStatus::AVAILABLE);
+    // TODO(sfiera): notify only when a category changed above.
+    NotifyNewSuggestions(category, item.second);
+  }
+
+  // TODO(sfiera): equivalent metrics for non-articles.
+  auto content_it = category_contents_.find(articles_category_);
+  DCHECK(content_it != category_contents_.end());
+  const CategoryContent& content = content_it->second;
+  UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles",
+                              content.snippets.size());
+  if (content.snippets.empty() && !content.dismissed.empty()) {
+    UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded",
+                         content.dismissed.size());
+  }
+
+  if (callback) {
+    callback->Run(status);
+  }
+}
+
+void RemoteSuggestionsProviderImpl::ArchiveSnippets(
+    CategoryContent* content,
+    NTPSnippet::PtrVector* to_archive) {
+  // Archive previous snippets - move them at the beginning of the list.
+  content->archived.insert(content->archived.begin(),
+                           std::make_move_iterator(to_archive->begin()),
+                           std::make_move_iterator(to_archive->end()));
+  to_archive->clear();
+
+  // If there are more archived snippets than we want to keep, delete the
+  // oldest ones by their fetch time (which are always in the back).
+  if (content->archived.size() > kMaxArchivedSnippetCount) {
+    NTPSnippet::PtrVector to_delete(
+        std::make_move_iterator(content->archived.begin() +
+                                kMaxArchivedSnippetCount),
+        std::make_move_iterator(content->archived.end()));
+    content->archived.resize(kMaxArchivedSnippetCount);
+    database_->DeleteImages(GetSnippetIDVector(to_delete));
+  }
+}
+
+void RemoteSuggestionsProviderImpl::SanitizeReceivedSnippets(
+    const NTPSnippet::PtrVector& dismissed,
+    NTPSnippet::PtrVector* snippets) {
+  DCHECK(ready());
+  EraseMatchingSnippets(snippets, dismissed);
+  RemoveIncompleteSnippets(snippets);
+}
+
+void RemoteSuggestionsProviderImpl::IntegrateSnippets(
+    CategoryContent* content,
+    NTPSnippet::PtrVector new_snippets) {
+  DCHECK(ready());
+
+  // Do not touch the current set of snippets if the newly fetched one is empty.
+  // TODO(tschumann): This should go. If we get empty results we should update
+  // accordingly and remove the old one (only of course if this was not received
+  // through a fetch-more).
+  if (new_snippets.empty()) {
+    return;
+  }
+
+  // It's entirely possible that the newly fetched snippets contain articles
+  // that have been present before.
+  // We need to make sure to only delete and archive snippets that don't
+  // appear with the same ID in the new suggestions (it's fine for additional
+  // IDs though).
+  EraseByPrimaryID(&content->snippets, *GetSnippetIDVector(new_snippets));
+  // Do not delete the thumbnail images as they are still handy on open NTPs.
+  database_->DeleteSnippets(GetSnippetIDVector(content->snippets));
+  // Note, that ArchiveSnippets will clear |content->snippets|.
+  ArchiveSnippets(content, &content->snippets);
+
+  database_->SaveSnippets(new_snippets);
+
+  content->snippets = std::move(new_snippets);
+}
+
+void RemoteSuggestionsProviderImpl::DismissSuggestionFromCategoryContent(
+    CategoryContent* content,
+    const std::string& id_within_category) {
+  auto it = std::find_if(
+      content->snippets.begin(), content->snippets.end(),
+      [&id_within_category](const std::unique_ptr<NTPSnippet>& snippet) {
+        return snippet->id() == id_within_category;
+      });
+  if (it == content->snippets.end()) {
+    return;
+  }
+
+  (*it)->set_dismissed(true);
+
+  database_->SaveSnippet(**it);
+
+  content->dismissed.push_back(std::move(*it));
+  content->snippets.erase(it);
+}
+
+void RemoteSuggestionsProviderImpl::ClearExpiredDismissedSnippets() {
+  std::vector<Category> categories_to_erase;
+
+  const base::Time now = base::Time::Now();
+
+  for (auto& item : category_contents_) {
+    Category category = item.first;
+    CategoryContent* content = &item.second;
+
+    NTPSnippet::PtrVector to_delete;
+    // Move expired dismissed snippets over into |to_delete|.
+    for (std::unique_ptr<NTPSnippet>& snippet : content->dismissed) {
+      if (snippet->expiry_date() <= now) {
+        to_delete.emplace_back(std::move(snippet));
+      }
+    }
+    RemoveNullPointers(&content->dismissed);
+
+    // Delete the images.
+    database_->DeleteImages(GetSnippetIDVector(to_delete));
+    // Delete the removed article suggestions from the DB.
+    database_->DeleteSnippets(GetSnippetIDVector(to_delete));
+
+    if (content->snippets.empty() && content->dismissed.empty() &&
+        category != articles_category_ &&
+        !content->included_in_last_server_response) {
+      categories_to_erase.push_back(category);
+    }
+  }
+
+  for (Category category : categories_to_erase) {
+    UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
+    category_contents_.erase(category);
+  }
+
+  StoreCategoriesToPrefs();
+}
+
+void RemoteSuggestionsProviderImpl::ClearOrphanedImages() {
+  auto alive_snippets = base::MakeUnique<std::set<std::string>>();
+  for (const auto& entry : category_contents_) {
+    const CategoryContent& content = entry.second;
+    for (const auto& snippet_ptr : content.snippets) {
+      alive_snippets->insert(snippet_ptr->id());
+    }
+    for (const auto& snippet_ptr : content.dismissed) {
+      alive_snippets->insert(snippet_ptr->id());
+    }
+  }
+  database_->GarbageCollectImages(std::move(alive_snippets));
+}
+
+void RemoteSuggestionsProviderImpl::NukeAllSnippets() {
+  std::vector<Category> categories_to_erase;
+
+  // Empty the ARTICLES category and remove all others, since they may or may
+  // not be personalized.
+  for (const auto& item : category_contents_) {
+    Category category = item.first;
+
+    ClearCachedSuggestions(category);
+    ClearDismissedSuggestionsForDebugging(category);
+
+    UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
+
+    // Remove the category entirely; it may or may not reappear.
+    if (category != articles_category_) {
+      categories_to_erase.push_back(category);
+    }
+  }
+
+  for (Category category : categories_to_erase) {
+    category_contents_.erase(category);
+  }
+
+  StoreCategoriesToPrefs();
+}
+
+void RemoteSuggestionsProviderImpl::FetchSuggestionImage(
+    const ContentSuggestion::ID& suggestion_id,
+    const ImageFetchedCallback& callback) {
+  if (!base::ContainsKey(category_contents_, suggestion_id.category())) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(callback, gfx::Image()));
+    return;
+  }
+  GURL image_url = FindSnippetImageUrl(suggestion_id);
+  if (image_url.is_empty()) {
+    // As we don't know the corresponding snippet anymore, we don't expect to
+    // find it in the database (and also can't fetch it remotely). Cut the
+    // lookup short and return directly.
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(callback, gfx::Image()));
+    return;
+  }
+  image_fetcher_.FetchSuggestionImage(suggestion_id, image_url, callback);
+}
+
+void RemoteSuggestionsProviderImpl::EnterStateReady() {
+  if (nuke_when_initialized_) {
+    NukeAllSnippets();
+    nuke_when_initialized_ = false;
+  }
+
+  auto article_category_it = category_contents_.find(articles_category_);
+  DCHECK(article_category_it != category_contents_.end());
+  if (article_category_it->second.snippets.empty() || fetch_when_ready_) {
+    // TODO(jkrcal): Fetching snippets automatically upon creation of this
+    // lazily created service can cause troubles, e.g. in unit tests where
+    // network I/O is not allowed.
+    // Either add a DCHECK here that we actually are allowed to do network I/O
+    // or change the logic so that some explicit call is always needed for the
+    // network request.
+    FetchSnippets(fetch_when_ready_interactive_,
+                  std::move(fetch_when_ready_callback_));
+    fetch_when_ready_ = false;
+  }
+
+  for (const auto& item : category_contents_) {
+    Category category = item.first;
+    const CategoryContent& content = item.second;
+    // FetchSnippets has set the status to |AVAILABLE_LOADING| if relevant,
+    // otherwise we transition to |AVAILABLE| here.
+    if (content.status != CategoryStatus::AVAILABLE_LOADING) {
+      UpdateCategoryStatus(category, CategoryStatus::AVAILABLE);
+    }
+  }
+}
+
+void RemoteSuggestionsProviderImpl::EnterStateDisabled() {
+  NukeAllSnippets();
+}
+
+void RemoteSuggestionsProviderImpl::EnterStateError() {
+  status_service_.reset();
+}
+
+void RemoteSuggestionsProviderImpl::FinishInitialization() {
+  if (nuke_when_initialized_) {
+    // We nuke here in addition to EnterStateReady, so that it happens even if
+    // we enter the DISABLED state below.
+    NukeAllSnippets();
+    nuke_when_initialized_ = false;
+  }
+
+  // Note: Initializing the status service will run the callback right away with
+  // the current state.
+  status_service_->Init(base::Bind(
+      &RemoteSuggestionsProviderImpl::OnStatusChanged, base::Unretained(this)));
+
+  // Always notify here even if we got nothing from the database, because we
+  // don't know how long the fetch will take or if it will even complete.
+  for (const auto& item : category_contents_) {
+    Category category = item.first;
+    const CategoryContent& content = item.second;
+    // Note: We might be in a non-available status here, e.g. DISABLED due to
+    // enterprise policy.
+    if (IsCategoryStatusAvailable(content.status)) {
+      NotifyNewSuggestions(category, content);
+    }
+  }
+}
+
+void RemoteSuggestionsProviderImpl::OnStatusChanged(
+    RemoteSuggestionsStatus old_status,
+    RemoteSuggestionsStatus new_status) {
+  switch (new_status) {
+    case RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN:
+      if (old_status == RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT) {
+        DCHECK(state_ == State::READY);
+        // Clear nonpersonalized suggestions.
+        NukeAllSnippets();
+        // Fetch personalized ones.
+        // TODO(jkrcal): Loop in SchedulingRemoteSuggestionsProvider somehow.
+        FetchSnippets(/*interactive_request=*/true,
+                      /*callback=*/nullptr);
+      } else {
+        // Do not change the status. That will be done in EnterStateReady().
+        EnterState(State::READY);
+      }
+      break;
+
+    case RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT:
+      if (old_status == RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN) {
+        DCHECK(state_ == State::READY);
+        // Clear personalized suggestions.
+        NukeAllSnippets();
+        // Fetch nonpersonalized ones.
+        // TODO(jkrcal): Loop in SchedulingRemoteSuggestionsProvider somehow.
+        FetchSnippets(/*interactive_request=*/true,
+                      /*callback=*/nullptr);
+      } else {
+        // Do not change the status. That will be done in EnterStateReady().
+        EnterState(State::READY);
+      }
+      break;
+
+    case RemoteSuggestionsStatus::EXPLICITLY_DISABLED:
+      EnterState(State::DISABLED);
+      UpdateAllCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
+      break;
+
+    case RemoteSuggestionsStatus::SIGNED_OUT_AND_DISABLED:
+      EnterState(State::DISABLED);
+      UpdateAllCategoryStatus(CategoryStatus::SIGNED_OUT);
+      break;
+  }
+}
+
+void RemoteSuggestionsProviderImpl::EnterState(State state) {
+  if (state == state_) {
+    return;
+  }
+
+  UMA_HISTOGRAM_ENUMERATION("NewTabPage.Snippets.EnteredState",
+                            static_cast<int>(state),
+                            static_cast<int>(State::COUNT));
+
+  switch (state) {
+    case State::NOT_INITED:
+      // Initial state, it should not be possible to get back there.
+      NOTREACHED();
+      break;
+
+    case State::READY:
+      DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED);
+
+      DVLOG(1) << "Entering state: READY";
+      state_ = State::READY;
+      EnterStateReady();
+      break;
+
+    case State::DISABLED:
+      DCHECK(state_ == State::NOT_INITED || state_ == State::READY);
+
+      DVLOG(1) << "Entering state: DISABLED";
+      state_ = State::DISABLED;
+      EnterStateDisabled();
+      break;
+
+    case State::ERROR_OCCURRED:
+      DVLOG(1) << "Entering state: ERROR_OCCURRED";
+      state_ = State::ERROR_OCCURRED;
+      EnterStateError();
+      break;
+
+    case State::COUNT:
+      NOTREACHED();
+      break;
+  }
+
+  NotifyStateChanged();
+}
+
+void RemoteSuggestionsProviderImpl::NotifyStateChanged() {
+  if (!provider_status_callback_) {
+    return;
+  }
+
+  switch (state_) {
+    case State::NOT_INITED:
+      // Initial state, not sure yet whether active or not.
+      break;
+    case State::READY:
+      provider_status_callback_->Run(ProviderStatus::ACTIVE);
+      break;
+    case State::DISABLED:
+      provider_status_callback_->Run(ProviderStatus::INACTIVE);
+      break;
+    case State::ERROR_OCCURRED:
+      provider_status_callback_->Run(ProviderStatus::INACTIVE);
+      break;
+    case State::COUNT:
+      NOTREACHED();
+      break;
+  }
+}
+
+void RemoteSuggestionsProviderImpl::NotifyNewSuggestions(
+    Category category,
+    const CategoryContent& content) {
+  DCHECK(IsCategoryStatusAvailable(content.status));
+
+  std::vector<ContentSuggestion> result =
+      ConvertToContentSuggestions(category, content.snippets);
+
+  DVLOG(1) << "NotifyNewSuggestions(): " << result.size()
+           << " items in category " << category;
+  observer()->OnNewSuggestions(this, category, std::move(result));
+}
+
+void RemoteSuggestionsProviderImpl::UpdateCategoryStatus(
+    Category category,
+    CategoryStatus status) {
+  auto content_it = category_contents_.find(category);
+  DCHECK(content_it != category_contents_.end());
+  CategoryContent& content = content_it->second;
+
+  if (status == content.status) {
+    return;
+  }
+
+  DVLOG(1) << "UpdateCategoryStatus(): " << category.id() << ": "
+           << static_cast<int>(content.status) << " -> "
+           << static_cast<int>(status);
+  content.status = status;
+  observer()->OnCategoryStatusChanged(this, category, content.status);
+}
+
+void RemoteSuggestionsProviderImpl::UpdateAllCategoryStatus(
+    CategoryStatus status) {
+  for (const auto& category : category_contents_) {
+    UpdateCategoryStatus(category.first, status);
+  }
+}
+
+namespace {
+
+template <typename T>
+typename T::const_iterator FindSnippetInContainer(
+    const T& container,
+    const std::string& id_within_category) {
+  return std::find_if(
+      container.begin(), container.end(),
+      [&id_within_category](const std::unique_ptr<NTPSnippet>& snippet) {
+        return snippet->id() == id_within_category;
+      });
+}
+
+}  // namespace
+
+const NTPSnippet* RemoteSuggestionsProviderImpl::CategoryContent::FindSnippet(
+    const std::string& id_within_category) const {
+  // Search for the snippet in current and archived snippets.
+  auto it = FindSnippetInContainer(snippets, id_within_category);
+  if (it != snippets.end()) {
+    return it->get();
+  }
+  auto archived_it = FindSnippetInContainer(archived, id_within_category);
+  if (archived_it != archived.end()) {
+    return archived_it->get();
+  }
+  auto dismissed_it = FindSnippetInContainer(dismissed, id_within_category);
+  if (dismissed_it != dismissed.end()) {
+    return dismissed_it->get();
+  }
+  return nullptr;
+}
+
+RemoteSuggestionsProviderImpl::CategoryContent*
+RemoteSuggestionsProviderImpl::UpdateCategoryInfo(Category category,
+                                                  const CategoryInfo& info) {
+  auto content_it = category_contents_.find(category);
+  if (content_it == category_contents_.end()) {
+    content_it = category_contents_
+                     .insert(std::make_pair(category, CategoryContent(info)))
+                     .first;
+  } else {
+    content_it->second.info = info;
+  }
+  return &content_it->second;
+}
+
+void RemoteSuggestionsProviderImpl::RestoreCategoriesFromPrefs() {
+  // This must only be called at startup, before there are any categories.
+  DCHECK(category_contents_.empty());
+
+  const base::ListValue* list =
+      pref_service_->GetList(prefs::kRemoteSuggestionCategories);
+  for (const std::unique_ptr<base::Value>& entry : *list) {
+    const base::DictionaryValue* dict = nullptr;
+    if (!entry->GetAsDictionary(&dict)) {
+      DLOG(WARNING) << "Invalid category pref value: " << *entry;
+      continue;
+    }
+    int id = 0;
+    if (!dict->GetInteger(kCategoryContentId, &id)) {
+      DLOG(WARNING) << "Invalid category pref value, missing '"
+                    << kCategoryContentId << "': " << *entry;
+      continue;
+    }
+    base::string16 title;
+    if (!dict->GetString(kCategoryContentTitle, &title)) {
+      DLOG(WARNING) << "Invalid category pref value, missing '"
+                    << kCategoryContentTitle << "': " << *entry;
+      continue;
+    }
+    bool included_in_last_server_response = false;
+    if (!dict->GetBoolean(kCategoryContentProvidedByServer,
+                          &included_in_last_server_response)) {
+      DLOG(WARNING) << "Invalid category pref value, missing '"
+                    << kCategoryContentProvidedByServer << "': " << *entry;
+      continue;
+    }
+    bool allow_fetching_more_results = false;
+    // This wasn't always around, so it's okay if it's missing.
+    dict->GetBoolean(kCategoryContentAllowFetchingMore,
+                     &allow_fetching_more_results);
+
+    Category category = Category::FromIDValue(id);
+    // The ranker may not persist the order of remote categories.
+    category_ranker_->AppendCategoryIfNecessary(category);
+    // TODO(tschumann): The following has a bad smell that category
+    // serialization / deserialization should not be done inside this
+    // class. We should move that into a central place that also knows how to
+    // parse data we received from remote backends.
+    CategoryInfo info =
+        category == articles_category_
+            ? BuildArticleCategoryInfo(title)
+            : BuildRemoteCategoryInfo(title, allow_fetching_more_results);
+    CategoryContent* content = UpdateCategoryInfo(category, info);
+    content->included_in_last_server_response =
+        included_in_last_server_response;
+  }
+}
+
+void RemoteSuggestionsProviderImpl::StoreCategoriesToPrefs() {
+  // Collect all the CategoryContents.
+  std::vector<std::pair<Category, const CategoryContent*>> to_store;
+  for (const auto& entry : category_contents_) {
+    to_store.emplace_back(entry.first, &entry.second);
+  }
+  // The ranker may not persist the order, thus, it is stored by the provider.
+  std::sort(to_store.begin(), to_store.end(),
+            [this](const std::pair<Category, const CategoryContent*>& left,
+                   const std::pair<Category, const CategoryContent*>& right) {
+              return category_ranker_->Compare(left.first, right.first);
+            });
+  // Convert the relevant info into a base::ListValue for storage.
+  base::ListValue list;
+  for (const auto& entry : to_store) {
+    const Category& category = entry.first;
+    const CategoryContent& content = *entry.second;
+    auto dict = base::MakeUnique<base::DictionaryValue>();
+    dict->SetInteger(kCategoryContentId, category.id());
+    // TODO(tschumann): Persist other properties of the CategoryInfo.
+    dict->SetString(kCategoryContentTitle, content.info.title());
+    dict->SetBoolean(kCategoryContentProvidedByServer,
+                     content.included_in_last_server_response);
+    dict->SetBoolean(kCategoryContentAllowFetchingMore,
+                     content.info.has_more_action());
+    list.Append(std::move(dict));
+  }
+  // Finally, store the result in the pref service.
+  pref_service_->Set(prefs::kRemoteSuggestionCategories, list);
+}
+
+RemoteSuggestionsProviderImpl::CategoryContent::CategoryContent(
+    const CategoryInfo& info)
+    : info(info) {}
+
+RemoteSuggestionsProviderImpl::CategoryContent::CategoryContent(
+    CategoryContent&&) = default;
+
+RemoteSuggestionsProviderImpl::CategoryContent::~CategoryContent() = default;
+
+RemoteSuggestionsProviderImpl::CategoryContent&
+RemoteSuggestionsProviderImpl::CategoryContent::operator=(CategoryContent&&) =
+    default;
+
+}  // namespace ntp_snippets
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_impl.h b/components/ntp_snippets/remote/remote_suggestions_provider_impl.h
new file mode 100644
index 0000000..9301dd8
--- /dev/null
+++ b/components/ntp_snippets/remote/remote_suggestions_provider_impl.h
@@ -0,0 +1,455 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_IMPL_H_
+#define COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_IMPL_H_
+
+#include <cstddef>
+#include <deque>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/time/clock.h"
+#include "base/time/time.h"
+#include "components/image_fetcher/image_fetcher_delegate.h"
+#include "components/ntp_snippets/category.h"
+#include "components/ntp_snippets/category_status.h"
+#include "components/ntp_snippets/content_suggestion.h"
+#include "components/ntp_snippets/content_suggestions_provider.h"
+#include "components/ntp_snippets/remote/ntp_snippet.h"
+#include "components/ntp_snippets/remote/ntp_snippets_fetcher.h"
+#include "components/ntp_snippets/remote/ntp_snippets_request_params.h"
+#include "components/ntp_snippets/remote/remote_suggestions_provider.h"
+#include "components/ntp_snippets/remote/remote_suggestions_status_service.h"
+#include "components/ntp_snippets/remote/request_throttler.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace gfx {
+class Image;
+}  // namespace gfx
+
+namespace image_fetcher {
+class ImageDecoder;
+class ImageFetcher;
+}  // namespace image_fetcher
+
+namespace ntp_snippets {
+
+class CategoryRanker;
+class RemoteSuggestionsDatabase;
+
+// CachedImageFetcher takes care of fetching images from the network and caching
+// them in the database.
+// TODO(tschumann): Move into a separate library and inject the
+// CachedImageFetcher into the RemoteSuggestionsProvider. This allows us to get
+// rid of exposing this member for testing and lets us test the caching logic
+// separately.
+class CachedImageFetcher : public image_fetcher::ImageFetcherDelegate {
+ public:
+  // |pref_service| and |database| need to outlive the created image fetcher
+  // instance.
+  CachedImageFetcher(std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
+                     std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
+                     PrefService* pref_service,
+                     RemoteSuggestionsDatabase* database);
+  ~CachedImageFetcher() override;
+
+  // Fetches the image for a suggestion. The fetcher will first issue a lookup
+  // to the underlying cache with a fallback to the network.
+  void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
+                            const GURL& image_url,
+                            const ImageFetchedCallback& callback);
+
+ private:
+  // image_fetcher::ImageFetcherDelegate implementation.
+  void OnImageDataFetched(const std::string& id_within_category,
+                          const std::string& image_data) override;
+  void OnImageDecodingDone(const ImageFetchedCallback& callback,
+                           const std::string& id_within_category,
+                           const gfx::Image& image);
+  void OnSnippetImageFetchedFromDatabase(
+      const ImageFetchedCallback& callback,
+      const ContentSuggestion::ID& suggestion_id,
+      const GURL& image_url,
+      // SnippetImageCallback requires nonconst reference
+      std::string data);
+  void OnSnippetImageDecodedFromDatabase(
+      const ImageFetchedCallback& callback,
+      const ContentSuggestion::ID& suggestion_id,
+      const GURL& url,
+      const gfx::Image& image);
+  void FetchSnippetImageFromNetwork(const ContentSuggestion::ID& suggestion_id,
+                                    const GURL& url,
+                                    const ImageFetchedCallback& callback);
+
+  std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher_;
+  std::unique_ptr<image_fetcher::ImageDecoder> image_decoder_;
+  RemoteSuggestionsDatabase* database_;
+  // Request throttler for limiting requests to thumbnail images.
+  RequestThrottler thumbnail_requests_throttler_;
+
+  DISALLOW_COPY_AND_ASSIGN(CachedImageFetcher);
+};
+
+// Retrieves fresh content data (articles) from the server, stores them and
+// provides them as content suggestions.
+// This class is final because it does things in its constructor which make it
+// unsafe to derive from it.
+// TODO(treib): Introduce two-phase initialization and make the class not final?
+class RemoteSuggestionsProviderImpl final : public RemoteSuggestionsProvider {
+ public:
+  // |application_language_code| should be a ISO 639-1 compliant string, e.g.
+  // 'en' or 'en-US'. Note that this code should only specify the language, not
+  // the locale, so 'en_US' (English language with US locale) and 'en-GB_US'
+  // (British English person in the US) are not language codes.
+  RemoteSuggestionsProviderImpl(
+      Observer* observer,
+      PrefService* pref_service,
+      const std::string& application_language_code,
+      CategoryRanker* category_ranker,
+      std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher,
+      std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
+      std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
+      std::unique_ptr<RemoteSuggestionsDatabase> database,
+      std::unique_ptr<RemoteSuggestionsStatusService> status_service);
+
+  ~RemoteSuggestionsProviderImpl() override;
+
+  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+  // Returns whether the service is ready. While this is false, the list of
+  // snippets will be empty, and all modifications to it (fetch, dismiss, etc)
+  // will be ignored.
+  bool ready() const { return state_ == State::READY; }
+
+  // Returns whether the service is successfully initialized. While this is
+  // false, some calls may trigger DCHECKs.
+  bool initialized() const { return ready() || state_ == State::DISABLED; }
+
+  // RemoteSuggestionsProvider implementation.
+  void SetProviderStatusCallback(
+      std::unique_ptr<ProviderStatusCallback> callback) override;
+  void RefetchInTheBackground(
+      std::unique_ptr<FetchStatusCallback> callback) override;
+
+  // TODO(fhorschig): Remove this getter when there is an interface for the
+  // fetcher that allows better mocks.
+  const NTPSnippetsFetcher* snippets_fetcher_for_testing_and_debugging()
+      const override;
+
+  // ContentSuggestionsProvider implementation.
+  CategoryStatus GetCategoryStatus(Category category) override;
+  CategoryInfo GetCategoryInfo(Category category) override;
+  void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
+  void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
+                            const ImageFetchedCallback& callback) override;
+  void Fetch(const Category& category,
+             const std::set<std::string>& known_suggestion_ids,
+             const FetchDoneCallback& callback) override;
+  void ReloadSuggestions() override;
+  void ClearHistory(
+      base::Time begin,
+      base::Time end,
+      const base::Callback<bool(const GURL& url)>& filter) override;
+  void ClearCachedSuggestions(Category category) override;
+  void OnSignInStateChanged() override;
+  void GetDismissedSuggestionsForDebugging(
+      Category category,
+      const DismissedSuggestionsCallback& callback) override;
+  void ClearDismissedSuggestionsForDebugging(Category category) override;
+
+  // Returns the maximum number of snippets that will be shown at once.
+  static int GetMaxSnippetCountForTesting();
+
+  // Available snippets, only for unit tests.
+  // TODO(treib): Get rid of this. Tests should use a fake observer instead.
+  const NTPSnippet::PtrVector& GetSnippetsForTesting(Category category) const {
+    return category_contents_.find(category)->second.snippets;
+  }
+
+  // Dismissed snippets, only for unit tests.
+  const NTPSnippet::PtrVector& GetDismissedSnippetsForTesting(
+      Category category) const {
+    return category_contents_.find(category)->second.dismissed;
+  }
+
+  // Overrides internal clock for testing purposes.
+  void SetClockForTesting(std::unique_ptr<base::Clock> clock) {
+    clock_ = std::move(clock);
+  }
+
+  // TODO(tschumann): remove this method as soon as we inject the fetcher into
+  // the constructor.
+  CachedImageFetcher& GetImageFetcherForTesting() { return image_fetcher_; }
+
+ private:
+  friend class RemoteSuggestionsProviderImplTest;
+
+  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderImplTest,
+                           CallsProviderStatusCallbackWhenReady);
+  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderImplTest,
+                           CallsProviderStatusCallbackOnError);
+  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderImplTest,
+                           CallsProviderStatusCallbackWhenDisabled);
+  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderImplTest,
+                           ShouldNotCrashWhenCallingEmptyCallback);
+  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderImplTest,
+                           DontNotifyIfNotAvailable);
+  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderImplTest,
+                           RemoveExpiredDismissedContent);
+  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderImplTest, StatusChanges);
+  FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderImplTest,
+                           SuggestionsFetchedOnSignInAndSignOut);
+
+  // Possible state transitions:
+  //       NOT_INITED --------+
+  //       /       \          |
+  //      v         v         |
+  //   READY <--> DISABLED    |
+  //       \       /          |
+  //        v     v           |
+  //     ERROR_OCCURRED <-----+
+  enum class State {
+    // The service has just been created. Can change to states:
+    // - DISABLED: After the database is done loading,
+    //             GetStateForDependenciesStatus can identify the next state to
+    //             be DISABLED.
+    // - READY: if GetStateForDependenciesStatus returns it, after the database
+    //          is done loading.
+    // - ERROR_OCCURRED: when an unrecoverable error occurred.
+    NOT_INITED,
+
+    // The service registered observers, timers, etc. and is ready to answer to
+    // queries, fetch snippets... Can change to states:
+    // - DISABLED: when the global Chrome state changes, for example after
+    //             |OnStateChanged| is called and sync is disabled.
+    // - ERROR_OCCURRED: when an unrecoverable error occurred.
+    READY,
+
+    // The service is disabled and unregistered the related resources.
+    // Can change to states:
+    // - READY: when the global Chrome state changes, for example after
+    //          |OnStateChanged| is called and sync is enabled.
+    // - ERROR_OCCURRED: when an unrecoverable error occurred.
+    DISABLED,
+
+    // The service or one of its dependencies encountered an unrecoverable error
+    // and the service can't be used anymore.
+    ERROR_OCCURRED,
+
+    COUNT
+  };
+
+  struct CategoryContent {
+    // The current status of the category.
+    CategoryStatus status = CategoryStatus::INITIALIZING;
+
+    // The additional information about a category.
+    CategoryInfo info;
+
+    // True iff the server returned results in this category in the last fetch.
+    // We never remove categories that the server still provides, but if the
+    // server stops providing a category, we won't yet report it as NOT_PROVIDED
+    // while we still have non-expired snippets in it.
+    bool included_in_last_server_response = true;
+
+    // All currently active suggestions (excl. the dismissed ones).
+    NTPSnippet::PtrVector snippets;
+
+    // All previous suggestions that we keep around in memory because they can
+    // be on some open NTP. We do not persist this list so that on a new start
+    // of Chrome, this is empty.
+    // |archived| is a FIFO buffer with a maximum length.
+    std::deque<std::unique_ptr<NTPSnippet>> archived;
+
+    // Suggestions that the user dismissed. We keep these around until they
+    // expire so we won't re-add them to |snippets| on the next fetch.
+    NTPSnippet::PtrVector dismissed;
+
+    // Returns a non-dismissed snippet with the given |id_within_category|, or
+    // null if none exist.
+    const NTPSnippet* FindSnippet(const std::string& id_within_category) const;
+
+    explicit CategoryContent(const CategoryInfo& info);
+    CategoryContent(CategoryContent&&);
+    ~CategoryContent();
+    CategoryContent& operator=(CategoryContent&&);
+  };
+
+  // Fetches snippets from the server and replaces old snippets by the new ones.
+  // Requests can be marked more important by setting |interactive_request| to
+  // true (such request might circumvent the daily quota for requests, etc.)
+  // Useful for requests triggered by the user. After the fetch finished, the
+  // provided |callback| will be triggered with the status of the fetch.
+  void FetchSnippets(bool interactive_request,
+                     std::unique_ptr<FetchStatusCallback> callback);
+
+  // Returns the URL of the image of a snippet if it is among the current or
+  // among the archived snippets in the matching category. Returns an empty URL
+  // otherwise.
+  GURL FindSnippetImageUrl(const ContentSuggestion::ID& suggestion_id) const;
+
+  // Callbacks for the RemoteSuggestionsDatabase.
+  void OnDatabaseLoaded(NTPSnippet::PtrVector snippets);
+  void OnDatabaseError();
+
+  // Callback for fetch-more requests with the NTPSnippetsFetcher.
+  void OnFetchMoreFinished(
+      const FetchDoneCallback& fetching_callback,
+      Status status,
+      NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories);
+
+  // Callback for regular fetch requests with the NTPSnippetsFetcher.
+  void OnFetchFinished(
+      std::unique_ptr<FetchStatusCallback> callback,
+      bool interactive_request,
+      Status status,
+      NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories);
+
+  // Moves all snippets from |to_archive| into the archive of the |content|.
+  // Clears |to_archive|. As the archive is a FIFO buffer of limited size, this
+  // function will also delete images from the database in case the associated
+  // snippet gets evicted from the archive.
+  void ArchiveSnippets(CategoryContent* content,
+                       NTPSnippet::PtrVector* to_archive);
+
+  // Sanitizes newly fetched snippets -- e.g. adding missing dates and filtering
+  // out incomplete results or dismissed snippets (indicated by |dismissed|).
+  void SanitizeReceivedSnippets(const NTPSnippet::PtrVector& dismissed,
+                                NTPSnippet::PtrVector* snippets);
+
+  // Adds newly available suggestions to |content|.
+  void IntegrateSnippets(CategoryContent* content,
+                         NTPSnippet::PtrVector new_snippets);
+
+  // Dismisses a snippet within a given category content.
+  // Note that this modifies the snippet datastructures of |content|
+  // invalidating iterators.
+  void DismissSuggestionFromCategoryContent(
+      CategoryContent* content,
+      const std::string& id_within_category);
+
+  // Removes expired dismissed snippets from the service and the database.
+  void ClearExpiredDismissedSnippets();
+
+  // Removes images from the DB that are not referenced from any known snippet.
+  // Needs to iterate the whole snippet database -- so do it often enough to
+  // keep it small but not too often as it still iterates over the file system.
+  void ClearOrphanedImages();
+
+  // Clears all stored snippets and updates the observer.
+  void NukeAllSnippets();
+
+  // Completes the initialization phase of the service, registering the last
+  // observers. This is done after construction, once the database is loaded.
+  void FinishInitialization();
+
+  // Triggers a state transition depending on the provided status. This method
+  // is called when a change is detected by |status_service_|.
+  void OnStatusChanged(RemoteSuggestionsStatus old_status,
+                       RemoteSuggestionsStatus new_status);
+
+  // Verifies state transitions (see |State|'s documentation) and applies them.
+  // Also updates the provider status. Does nothing except updating the provider
+  // status if called with the current state.
+  void EnterState(State state);
+
+  // Notifies the state change to ProviderStatusCallback specified by
+  // SetProviderStatusCallback().
+  void NotifyStateChanged();
+
+  // Enables the service. Do not call directly, use |EnterState| instead.
+  void EnterStateReady();
+
+  // Disables the service. Do not call directly, use |EnterState| instead.
+  void EnterStateDisabled();
+
+  // Disables the service permanently because an unrecoverable error occurred.
+  // Do not call directly, use |EnterState| instead.
+  void EnterStateError();
+
+  // Converts the cached snippets in the given |category| to content suggestions
+  // and notifies the observer.
+  void NotifyNewSuggestions(Category category, const CategoryContent& content);
+
+  // Updates the internal status for |category| to |category_status_| and
+  // notifies the content suggestions observer if it changed.
+  void UpdateCategoryStatus(Category category, CategoryStatus status);
+  // Calls UpdateCategoryStatus() for all provided categories.
+  void UpdateAllCategoryStatus(CategoryStatus status);
+
+  // Updates the category info for |category|. If a corresponding
+  // CategoryContent object does not exist, it will be created.
+  // Returns the existing or newly created object.
+  CategoryContent* UpdateCategoryInfo(Category category,
+                                      const CategoryInfo& info);
+
+  void RestoreCategoriesFromPrefs();
+  void StoreCategoriesToPrefs();
+
+  NTPSnippetsRequestParams BuildFetchParams() const;
+
+  void MarkEmptyCategoriesAsLoading();
+
+  State state_;
+
+  PrefService* pref_service_;
+
+  const Category articles_category_;
+
+  std::map<Category, CategoryContent, Category::CompareByID> category_contents_;
+
+  // The ISO 639-1 code of the language used by the application.
+  const std::string application_language_code_;
+
+  // Ranker that orders the categories. Not owned.
+  CategoryRanker* category_ranker_;
+
+  // The snippets fetcher.
+  std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher_;
+
+  // The database for persisting snippets.
+  std::unique_ptr<RemoteSuggestionsDatabase> database_;
+  base::TimeTicks database_load_start_;
+
+  // The image fetcher.
+  CachedImageFetcher image_fetcher_;
+
+  // The service that provides events and data about the signin and sync state.
+  std::unique_ptr<RemoteSuggestionsStatusService> status_service_;
+
+  // Set to true if FetchSnippets is called while the service isn't ready.
+  // The fetch will be executed once the service enters the READY state.
+  // TODO(jkrcal): create a struct and have here just one base::Optional<>?
+  bool fetch_when_ready_;
+
+  // The parameters for the fetch to perform later.
+  bool fetch_when_ready_interactive_;
+  std::unique_ptr<FetchStatusCallback> fetch_when_ready_callback_;
+
+  std::unique_ptr<ProviderStatusCallback> provider_status_callback_;
+
+  // Set to true if NukeAllSnippets is called while the service isn't ready.
+  // The nuke will be executed once the service finishes initialization or
+  // enters the READY state.
+  bool nuke_when_initialized_;
+
+  // A clock for getting the time. This allows to inject a clock in tests.
+  std::unique_ptr<base::Clock> clock_;
+
+  DISALLOW_COPY_AND_ASSIGN(RemoteSuggestionsProviderImpl);
+};
+
+}  // namespace ntp_snippets
+
+#endif  // COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_IMPL_H_
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc b/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
similarity index 85%
rename from components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc
rename to components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
index 1e14f2a..804d1a09 100644
--- a/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/ntp_snippets/remote/remote_suggestions_provider.h"
+#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
 
 #include <memory>
 #include <utility>
@@ -35,7 +35,7 @@
 #include "components/ntp_snippets/pref_names.h"
 #include "components/ntp_snippets/remote/ntp_snippet.h"
 #include "components/ntp_snippets/remote/ntp_snippets_fetcher.h"
-#include "components/ntp_snippets/remote/ntp_snippets_scheduler.h"
+#include "components/ntp_snippets/remote/persistent_scheduler.h"
 #include "components/ntp_snippets/remote/remote_suggestions_database.h"
 #include "components/ntp_snippets/remote/test_utils.h"
 #include "components/ntp_snippets/user_classifier.h"
@@ -65,6 +65,7 @@
 using testing::SaveArg;
 using testing::SizeIs;
 using testing::StartsWith;
+using testing::StrictMock;
 using testing::WithArgs;
 
 namespace ntp_snippets {
@@ -289,7 +290,7 @@
   notify->OnImageDataFetched(id, "1-by-1-image-data");
 }
 
-gfx::Image FetchImage(RemoteSuggestionsProvider* service,
+gfx::Image FetchImage(RemoteSuggestionsProviderImpl* service,
                       const ContentSuggestion::ID& suggestion_id) {
   gfx::Image result;
   base::RunLoop run_loop;
@@ -331,14 +332,6 @@
   }
 };
 
-class MockScheduler : public NTPSnippetsScheduler {
- public:
-  MOCK_METHOD2(Schedule,
-               bool(base::TimeDelta period_wifi,
-                    base::TimeDelta period_fallback));
-  MOCK_METHOD0(Unschedule, bool());
-};
-
 class MockImageFetcher : public ImageFetcher {
  public:
   MOCK_METHOD1(SetImageFetcherDelegate, void(ImageFetcherDelegate*));
@@ -415,9 +408,9 @@
 
 }  // namespace
 
-class RemoteSuggestionsProviderTest : public ::testing::Test {
+class RemoteSuggestionsProviderImplTest : public ::testing::Test {
  public:
-  RemoteSuggestionsProviderTest()
+  RemoteSuggestionsProviderImplTest()
       : params_manager_(ntp_snippets::kStudyName,
                         {{"content_suggestions_backend",
                           kTestContentSuggestionsServerEndpoint},
@@ -430,14 +423,14 @@
         image_fetcher_(nullptr),
         image_decoder_(nullptr),
         database_(nullptr) {
-    RemoteSuggestionsProvider::RegisterProfilePrefs(
+    RemoteSuggestionsProviderImpl::RegisterProfilePrefs(
         utils_.pref_service()->registry());
     RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry());
 
     EXPECT_TRUE(database_dir_.CreateUniqueTempDir());
   }
 
-  ~RemoteSuggestionsProviderTest() override {
+  ~RemoteSuggestionsProviderImplTest() override {
     // We need to run the message loop after deleting the database, because
     // ProtoDatabaseImpl deletes the actual LevelDB asynchronously on the task
     // runner. Without this, we'd get reports of memory leaks.
@@ -446,14 +439,14 @@
 
   // TODO(vitaliii): Rewrite this function to initialize a test class member
   // instead of creating a new service.
-  std::unique_ptr<RemoteSuggestionsProvider> MakeSnippetsService(
+  std::unique_ptr<RemoteSuggestionsProviderImpl> MakeSnippetsService(
       bool set_empty_response = true) {
     auto service = MakeSnippetsServiceWithoutInitialization();
     WaitForSnippetsServiceInitialization(service.get(), set_empty_response);
     return service;
   }
 
-  std::unique_ptr<RemoteSuggestionsProvider>
+  std::unique_ptr<RemoteSuggestionsProviderImpl>
   MakeSnippetsServiceWithoutInitialization() {
     scoped_refptr<base::SingleThreadTaskRunner> task_runner(
         base::ThreadTaskRunnerHandle::Get());
@@ -480,17 +473,19 @@
     auto database = base::MakeUnique<RemoteSuggestionsDatabase>(
         database_dir_.GetPath(), task_runner);
     database_ = database.get();
-    return base::MakeUnique<RemoteSuggestionsProvider>(
+    return base::MakeUnique<RemoteSuggestionsProviderImpl>(
         observer_.get(), utils_.pref_service(), "fr", category_ranker_.get(),
-        &user_classifier_, &scheduler_, std::move(snippets_fetcher),
-        std::move(image_fetcher), std::move(image_decoder), std::move(database),
+        std::move(snippets_fetcher), std::move(image_fetcher),
+        std::move(image_decoder), std::move(database),
         base::MakeUnique<RemoteSuggestionsStatusService>(
             utils_.fake_signin_manager(), utils_.pref_service()));
   }
 
-  void WaitForSnippetsServiceInitialization(RemoteSuggestionsProvider* service,
-                                            bool set_empty_response) {
-    EXPECT_EQ(RemoteSuggestionsProvider::State::NOT_INITED, service->state_);
+  void WaitForSnippetsServiceInitialization(
+      RemoteSuggestionsProviderImpl* service,
+      bool set_empty_response) {
+    EXPECT_EQ(RemoteSuggestionsProviderImpl::State::NOT_INITED,
+              service->state_);
 
     // Add an initial fetch response, as the service tries to fetch when there
     // is nothing in the DB.
@@ -500,11 +495,13 @@
 
     // TODO(treib): Find a better way to wait for initialization to finish.
     base::RunLoop().RunUntilIdle();
-    EXPECT_NE(RemoteSuggestionsProvider::State::NOT_INITED, service->state_);
+    EXPECT_NE(RemoteSuggestionsProviderImpl::State::NOT_INITED,
+              service->state_);
   }
 
-  void ResetSnippetsService(std::unique_ptr<RemoteSuggestionsProvider>* service,
-                            bool set_empty_response) {
+  void ResetSnippetsService(
+      std::unique_ptr<RemoteSuggestionsProviderImpl>* service,
+      bool set_empty_response) {
     service->reset();
     observer_.reset();
     *service = MakeSnippetsService(set_empty_response);
@@ -537,7 +534,6 @@
  protected:
   const GURL& test_url() { return test_url_; }
   FakeContentSuggestionsProviderObserver& observer() { return *observer_; }
-  MockScheduler& mock_scheduler() { return scheduler_; }
   // TODO(tschumann): Make this a strict-mock. We want to avoid unneccesary
   // network requests.
   NiceMock<MockImageFetcher>* image_fetcher() { return image_fetcher_; }
@@ -558,14 +554,15 @@
                                               net::URLRequestStatus::SUCCESS);
   }
 
-  void LoadFromJSONString(RemoteSuggestionsProvider* service,
+  void LoadFromJSONString(RemoteSuggestionsProviderImpl* service,
                           const std::string& json) {
     SetUpFetchResponse(json);
-    service->FetchSnippets(true);
+    service->FetchSnippets(/*interactive_request=*/true,
+                           /*callback=*/nullptr);
     base::RunLoop().RunUntilIdle();
   }
 
-  void LoadMoreFromJSONString(RemoteSuggestionsProvider* service,
+  void LoadMoreFromJSONString(RemoteSuggestionsProviderImpl* service,
                               const Category& category,
                               const std::string& json,
                               const std::set<std::string>& known_ids,
@@ -586,7 +583,6 @@
   std::unique_ptr<OAuth2TokenService> fake_token_service_;
   std::unique_ptr<CategoryRanker> category_ranker_;
   UserClassifier user_classifier_;
-  NiceMock<MockScheduler> scheduler_;
   std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_;
   NiceMock<MockImageFetcher>* image_fetcher_;
   FakeImageDecoder* image_decoder_;
@@ -594,127 +590,10 @@
   base::ScopedTempDir database_dir_;
   RemoteSuggestionsDatabase* database_;
 
-  DISALLOW_COPY_AND_ASSIGN(RemoteSuggestionsProviderTest);
+  DISALLOW_COPY_AND_ASSIGN(RemoteSuggestionsProviderImplTest);
 };
 
-TEST_F(RemoteSuggestionsProviderTest, ScheduleOnStart) {
-  // We should get two |Schedule| calls: The first when initialization
-  // completes, the second one after the automatic (since the service doesn't
-  // have any data yet) fetch finishes.
-  EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(2);
-  EXPECT_CALL(mock_scheduler(), Unschedule()).Times(0);
-  auto service = MakeSnippetsService();
-
-  // When we have no snippets are all, loading the service initiates a fetch.
-  EXPECT_EQ("OK", service->snippets_fetcher()->last_status());
-}
-
-TEST_F(RemoteSuggestionsProviderTest, DontRescheduleOnStart) {
-  EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(2);
-  EXPECT_CALL(mock_scheduler(), Unschedule()).Times(0);
-  SetUpFetchResponse(GetTestJson({GetSnippet()}));
-  auto service = MakeSnippetsService(/*set_empty_response=*/false);
-
-  // When recreating the service, we should not get any |Schedule| calls:
-  // The tasks are already scheduled with the correct intervals, so nothing on
-  // initialization, and the service has data from the DB, so no automatic fetch
-  // should happen.
-  Mock::VerifyAndClearExpectations(&mock_scheduler());
-  EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(0);
-  EXPECT_CALL(mock_scheduler(), Unschedule()).Times(0);
-  ResetSnippetsService(&service, /*set_empty_response=*/true);
-}
-
-TEST_F(RemoteSuggestionsProviderTest, RescheduleAfterSuccessfulFetch) {
-  // We should get two |Schedule| calls: The first when initialization
-  // completes, the second one after the automatic (since the service doesn't
-  // have any data yet) fetch finishes.
-  EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(2);
-  auto service = MakeSnippetsService();
-
-  // A successful fetch should trigger another |Schedule|.
-  EXPECT_CALL(mock_scheduler(), Schedule(_, _));
-  LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
-}
-
-TEST_F(RemoteSuggestionsProviderTest, DontRescheduleAfterFailedFetch) {
-  // We should get two |Schedule| calls: The first when initialization
-  // completes, the second one after the automatic (since the service doesn't
-  // have any data yet) fetch finishes.
-  EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(2);
-  auto service = MakeSnippetsService();
-
-  // A failed fetch should NOT trigger another |Schedule|.
-  EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(0);
-  LoadFromJSONString(service.get(), GetTestJson({GetInvalidSnippet()}));
-}
-
-TEST_F(RemoteSuggestionsProviderTest, IgnoreRescheduleBeforeInit) {
-  // We should get two |Schedule| calls: The first when initialization
-  // completes, the second one after the automatic (since the service doesn't
-  // have any data yet) fetch finishes.
-  EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(2);
-  // The |RescheduleFetching| call shouldn't do anything (in particular not
-  // result in an |Unschedule|), since the service isn't initialized yet.
-  EXPECT_CALL(mock_scheduler(), Unschedule()).Times(0);
-  auto service = MakeSnippetsServiceWithoutInitialization();
-  service->RescheduleFetching(false);
-  WaitForSnippetsServiceInitialization(service.get(),
-                                       /*set_empty_response=*/true);
-}
-
-TEST_F(RemoteSuggestionsProviderTest, HandleForcedRescheduleBeforeInit) {
-  {
-    InSequence s;
-    // The |RescheduleFetching| call with force=true should result in an
-    // |Unschedule|, since the service isn't initialized yet.
-    EXPECT_CALL(mock_scheduler(), Unschedule()).Times(1);
-    // We should get two |Schedule| calls: The first when initialization
-    // completes, the second one after the automatic (since the service doesn't
-    // have any data yet) fetch finishes.
-    EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(2);
-  }
-  auto service = MakeSnippetsServiceWithoutInitialization();
-  service->RescheduleFetching(true);
-  WaitForSnippetsServiceInitialization(service.get(),
-                                       /*set_empty_response=*/true);
-}
-
-TEST_F(RemoteSuggestionsProviderTest, RescheduleOnStateChange) {
-  {
-    InSequence s;
-    // Initial startup.
-    EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(2);
-    // Service gets disabled.
-    EXPECT_CALL(mock_scheduler(), Unschedule());
-    // Service gets enabled again.
-    EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(2);
-  }
-  auto service = MakeSnippetsService();
-  ASSERT_TRUE(service->ready());
-
-  service->OnStatusChanged(RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN,
-                           RemoteSuggestionsStatus::EXPLICITLY_DISABLED);
-  ASSERT_FALSE(service->ready());
-  base::RunLoop().RunUntilIdle();
-
-  service->OnStatusChanged(RemoteSuggestionsStatus::EXPLICITLY_DISABLED,
-                           RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT);
-  ASSERT_TRUE(service->ready());
-  base::RunLoop().RunUntilIdle();
-}
-
-TEST_F(RemoteSuggestionsProviderTest, DontUnscheduleOnShutdown) {
-  EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(2);
-  EXPECT_CALL(mock_scheduler(), Unschedule()).Times(0);
-
-  auto service = MakeSnippetsService();
-
-  service.reset();
-  base::RunLoop().RunUntilIdle();
-}
-
-TEST_F(RemoteSuggestionsProviderTest, Full) {
+TEST_F(RemoteSuggestionsProviderImplTest, Full) {
   std::string json_str(GetTestJson({GetSnippet()}));
 
   auto service = MakeSnippetsService();
@@ -736,7 +615,7 @@
             base::UTF16ToUTF8(suggestion.publisher_name()));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, CategoryTitle) {
+TEST_F(RemoteSuggestionsProviderImplTest, CategoryTitle) {
   const base::string16 test_default_title =
       base::UTF8ToUTF16(kTestJsonDefaultCategoryTitle);
 
@@ -771,7 +650,7 @@
   EXPECT_THAT(info_before.show_if_empty(), Eq(true));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, MultipleCategories) {
+TEST_F(RemoteSuggestionsProviderImplTest, MultipleCategories) {
   auto service = MakeSnippetsService();
   std::string json_str =
       MultiCategoryJsonBuilder()
@@ -817,7 +696,7 @@
   }
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ArticleCategoryInfo) {
+TEST_F(RemoteSuggestionsProviderImplTest, ArticleCategoryInfo) {
   auto service = MakeSnippetsService();
   CategoryInfo article_info = service->GetCategoryInfo(articles_category());
   EXPECT_THAT(article_info.has_more_action(), Eq(true));
@@ -826,7 +705,7 @@
   EXPECT_THAT(article_info.show_if_empty(), Eq(true));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ExperimentalCategoryInfo) {
+TEST_F(RemoteSuggestionsProviderImplTest, ExperimentalCategoryInfo) {
   auto service = MakeSnippetsService();
   std::string json_str =
       MultiCategoryJsonBuilder()
@@ -844,7 +723,7 @@
   EXPECT_THAT(info.show_if_empty(), Eq(false));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, AddRemoteCategoriesToCategoryRanker) {
+TEST_F(RemoteSuggestionsProviderImplTest, AddRemoteCategoriesToCategoryRanker) {
   auto mock_ranker = base::MakeUnique<MockCategoryRanker>();
   MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
   SetCategoryRanker(std::move(mock_ranker));
@@ -869,7 +748,7 @@
   auto service = MakeSnippetsService(/*set_empty_response=*/false);
 }
 
-TEST_F(RemoteSuggestionsProviderTest, PersistCategoryInfos) {
+TEST_F(RemoteSuggestionsProviderImplTest, PersistCategoryInfos) {
   auto service = MakeSnippetsService();
   // TODO(vitaliii): Use |articles_category()| instead of constant ID below.
   std::string json_str =
@@ -914,7 +793,7 @@
   EXPECT_EQ(info_unknown_before.title(), info_unknown_after.title());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, PersistRemoteCategoryOrder) {
+TEST_F(RemoteSuggestionsProviderImplTest, PersistRemoteCategoryOrder) {
   // We create a service with a normal ranker to store the order.
   std::string json_str =
       MultiCategoryJsonBuilder()
@@ -950,7 +829,7 @@
   ResetSnippetsService(&service, /*set_empty_response=*/false);
 }
 
-TEST_F(RemoteSuggestionsProviderTest, PersistSuggestions) {
+TEST_F(RemoteSuggestionsProviderImplTest, PersistSuggestions) {
   auto service = MakeSnippetsService();
   std::string json_str =
       MultiCategoryJsonBuilder()
@@ -972,7 +851,7 @@
   EXPECT_THAT(observer().SuggestionsForCategory(other_category()), SizeIs(1));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, DontNotifyIfNotAvailable) {
+TEST_F(RemoteSuggestionsProviderImplTest, DontNotifyIfNotAvailable) {
   // Get some suggestions into the database.
   auto service = MakeSnippetsService();
   std::string json_str =
@@ -995,7 +874,8 @@
   // Recreate the service to simulate a Chrome start.
   ResetSnippetsService(&service, /*set_empty_response=*/true);
 
-  ASSERT_THAT(RemoteSuggestionsProvider::State::DISABLED, Eq(service->state_));
+  ASSERT_THAT(RemoteSuggestionsProviderImpl::State::DISABLED,
+              Eq(service->state_));
 
   // Now the observer should not have received any suggestions.
   EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
@@ -1003,7 +883,7 @@
   EXPECT_THAT(observer().SuggestionsForCategory(other_category()), IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, Clear) {
+TEST_F(RemoteSuggestionsProviderImplTest, Clear) {
   auto service = MakeSnippetsService();
 
   std::string json_str(GetTestJson({GetSnippet()}));
@@ -1015,7 +895,7 @@
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ReplaceSnippets) {
+TEST_F(RemoteSuggestionsProviderImplTest, ReplaceSnippets) {
   auto service = MakeSnippetsService();
 
   std::string first("http://first");
@@ -1030,7 +910,7 @@
               ElementsAre(IdEq(second)));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, LoadsAdditionalSnippets) {
+TEST_F(RemoteSuggestionsProviderImplTest, LoadsAdditionalSnippets) {
   auto service = MakeSnippetsService();
 
   LoadFromJSONString(service.get(),
@@ -1080,7 +960,8 @@
 // TODO(tschumann): Test step 4) on a higher level instead of peeking into the
 // internal 'dismissed' data. The proper check is to make sure we tell the
 // backend to exclude these snippets.
-TEST_F(RemoteSuggestionsProviderTest, TestMergingFetchedMoreSnippetsFillup) {
+TEST_F(RemoteSuggestionsProviderImplTest,
+       TestMergingFetchedMoreSnippetsFillup) {
   auto service = MakeSnippetsService(/*set_empty_response=*/false);
   LoadFromJSONString(
       service.get(),
@@ -1133,7 +1014,7 @@
               ElementsAre(IdEq("http://id-1"), IdEq("http://id-2")));
 }
 
-TEST_F(RemoteSuggestionsProviderTest,
+TEST_F(RemoteSuggestionsProviderImplTest,
        TestMergingFetchedMoreSnippetsReplaceAll) {
   auto service = MakeSnippetsService(/*set_empty_response=*/false);
   LoadFromJSONString(
@@ -1157,17 +1038,17 @@
 
   auto expect_receiving_ten_new_snippets =
       base::Bind([](Status status, std::vector<ContentSuggestion> suggestions) {
-        EXPECT_THAT(suggestions, ElementsAre(
-            IdWithinCategoryEq("http://more-id-1"),
-            IdWithinCategoryEq("http://more-id-2"),
-            IdWithinCategoryEq("http://more-id-3"),
-            IdWithinCategoryEq("http://more-id-4"),
-            IdWithinCategoryEq("http://more-id-5"),
-            IdWithinCategoryEq("http://more-id-6"),
-            IdWithinCategoryEq("http://more-id-7"),
-            IdWithinCategoryEq("http://more-id-8"),
-            IdWithinCategoryEq("http://more-id-9"),
-            IdWithinCategoryEq("http://more-id-10")));
+        EXPECT_THAT(suggestions,
+                    ElementsAre(IdWithinCategoryEq("http://more-id-1"),
+                                IdWithinCategoryEq("http://more-id-2"),
+                                IdWithinCategoryEq("http://more-id-3"),
+                                IdWithinCategoryEq("http://more-id-4"),
+                                IdWithinCategoryEq("http://more-id-5"),
+                                IdWithinCategoryEq("http://more-id-6"),
+                                IdWithinCategoryEq("http://more-id-7"),
+                                IdWithinCategoryEq("http://more-id-8"),
+                                IdWithinCategoryEq("http://more-id-9"),
+                                IdWithinCategoryEq("http://more-id-10")));
       });
   LoadMoreFromJSONString(
       service.get(), articles_category(),
@@ -1222,7 +1103,7 @@
 
 }  // namespace
 
-TEST_F(RemoteSuggestionsProviderTest, ReturnFetchRequestEmptyBeforeInit) {
+TEST_F(RemoteSuggestionsProviderImplTest, ReturnFetchRequestEmptyBeforeInit) {
   auto service = MakeSnippetsServiceWithoutInitialization();
   MockFunction<void(Status, const std::vector<ContentSuggestion>&)> loaded;
   EXPECT_CALL(loaded, Call(HasCode(StatusCode::TEMPORARY_ERROR), IsEmpty()));
@@ -1231,7 +1112,7 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ReturnTemporaryErrorForInvalidJson) {
+TEST_F(RemoteSuggestionsProviderImplTest, ReturnTemporaryErrorForInvalidJson) {
   auto service = MakeSnippetsService();
 
   MockFunction<void(Status, const std::vector<ContentSuggestion>&)> loaded;
@@ -1240,11 +1121,13 @@
                          "invalid json string}]}",
                          /*known_ids=*/std::set<std::string>(),
                          base::Bind(&SuggestionsLoaded, &loaded));
-  EXPECT_THAT(service->snippets_fetcher()->last_status(),
-              StartsWith("Received invalid JSON"));
+  EXPECT_THAT(
+      service->snippets_fetcher_for_testing_and_debugging()->last_status(),
+      StartsWith("Received invalid JSON"));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ReturnTemporaryErrorForInvalidSnippet) {
+TEST_F(RemoteSuggestionsProviderImplTest,
+       ReturnTemporaryErrorForInvalidSnippet) {
   auto service = MakeSnippetsService();
 
   MockFunction<void(Status, const std::vector<ContentSuggestion>&)> loaded;
@@ -1253,11 +1136,13 @@
                          GetTestJson({GetIncompleteSnippet()}),
                          /*known_ids=*/std::set<std::string>(),
                          base::Bind(&SuggestionsLoaded, &loaded));
-  EXPECT_THAT(service->snippets_fetcher()->last_status(),
-              StartsWith("Invalid / empty list"));
+  EXPECT_THAT(
+      service->snippets_fetcher_for_testing_and_debugging()->last_status(),
+      StartsWith("Invalid / empty list"));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ReturnTemporaryErrorForRequestFailure) {
+TEST_F(RemoteSuggestionsProviderImplTest,
+       ReturnTemporaryErrorForRequestFailure) {
   // Created SnippetsService will fail by default with unsuccessful request.
   auto service = MakeSnippetsService(/*set_empty_response=*/false);
 
@@ -1269,7 +1154,7 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ReturnTemporaryErrorForHttpFailure) {
+TEST_F(RemoteSuggestionsProviderImplTest, ReturnTemporaryErrorForHttpFailure) {
   auto service = MakeSnippetsService();
   SetUpHttpError();
 
@@ -1281,52 +1166,59 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(RemoteSuggestionsProviderTest, LoadInvalidJson) {
+TEST_F(RemoteSuggestionsProviderImplTest, LoadInvalidJson) {
   auto service = MakeSnippetsService();
 
   LoadFromJSONString(service.get(), GetTestJson({GetInvalidSnippet()}));
-  EXPECT_THAT(service->snippets_fetcher()->last_status(),
-              StartsWith("Received invalid JSON"));
+  EXPECT_THAT(
+      service->snippets_fetcher_for_testing_and_debugging()->last_status(),
+      StartsWith("Received invalid JSON"));
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, LoadInvalidJsonWithExistingSnippets) {
+TEST_F(RemoteSuggestionsProviderImplTest, LoadInvalidJsonWithExistingSnippets) {
   auto service = MakeSnippetsService();
 
   LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
   ASSERT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(1));
-  ASSERT_EQ("OK", service->snippets_fetcher()->last_status());
+  ASSERT_EQ(
+      "OK",
+      service->snippets_fetcher_for_testing_and_debugging()->last_status());
 
   LoadFromJSONString(service.get(), GetTestJson({GetInvalidSnippet()}));
-  EXPECT_THAT(service->snippets_fetcher()->last_status(),
-              StartsWith("Received invalid JSON"));
+  EXPECT_THAT(
+      service->snippets_fetcher_for_testing_and_debugging()->last_status(),
+      StartsWith("Received invalid JSON"));
   // This should not have changed the existing snippets.
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(1));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, LoadIncompleteJson) {
+TEST_F(RemoteSuggestionsProviderImplTest, LoadIncompleteJson) {
   auto service = MakeSnippetsService();
 
   LoadFromJSONString(service.get(), GetTestJson({GetIncompleteSnippet()}));
-  EXPECT_EQ("Invalid / empty list.",
-            service->snippets_fetcher()->last_status());
+  EXPECT_EQ(
+      "Invalid / empty list.",
+      service->snippets_fetcher_for_testing_and_debugging()->last_status());
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, LoadIncompleteJsonWithExistingSnippets) {
+TEST_F(RemoteSuggestionsProviderImplTest,
+       LoadIncompleteJsonWithExistingSnippets) {
   auto service = MakeSnippetsService();
 
   LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
   ASSERT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(1));
 
   LoadFromJSONString(service.get(), GetTestJson({GetIncompleteSnippet()}));
-  EXPECT_EQ("Invalid / empty list.",
-            service->snippets_fetcher()->last_status());
+  EXPECT_EQ(
+      "Invalid / empty list.",
+      service->snippets_fetcher_for_testing_and_debugging()->last_status());
   // This should not have changed the existing snippets.
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(1));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, Dismiss) {
+TEST_F(RemoteSuggestionsProviderImplTest, Dismiss) {
   auto service = MakeSnippetsService();
 
   std::string json_str(
@@ -1377,7 +1269,7 @@
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(1));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, GetDismissed) {
+TEST_F(RemoteSuggestionsProviderImplTest, GetDismissed) {
   auto service = MakeSnippetsService();
 
   LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
@@ -1387,8 +1279,8 @@
   service->GetDismissedSuggestionsForDebugging(
       articles_category(),
       base::Bind(
-          [](RemoteSuggestionsProvider* service,
-             RemoteSuggestionsProviderTest* test,
+          [](RemoteSuggestionsProviderImpl* service,
+             RemoteSuggestionsProviderImplTest* test,
              std::vector<ContentSuggestion> dismissed_suggestions) {
             EXPECT_EQ(1u, dismissed_suggestions.size());
             for (auto& suggestion : dismissed_suggestions) {
@@ -1403,8 +1295,8 @@
   service->GetDismissedSuggestionsForDebugging(
       articles_category(),
       base::Bind(
-          [](RemoteSuggestionsProvider* service,
-             RemoteSuggestionsProviderTest* test,
+          [](RemoteSuggestionsProviderImpl* service,
+             RemoteSuggestionsProviderImplTest* test,
              std::vector<ContentSuggestion> dismissed_suggestions) {
             EXPECT_EQ(0u, dismissed_suggestions.size());
           },
@@ -1412,7 +1304,7 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(RemoteSuggestionsProviderTest, CreationTimestampParseFail) {
+TEST_F(RemoteSuggestionsProviderImplTest, CreationTimestampParseFail) {
   auto service = MakeSnippetsService();
 
   std::string json =
@@ -1425,7 +1317,7 @@
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, RemoveExpiredDismissedContent) {
+TEST_F(RemoteSuggestionsProviderImplTest, RemoveExpiredDismissedContent) {
   auto service = MakeSnippetsService();
 
   std::string json_str1(GetTestJson({GetExpiredSnippet()}));
@@ -1458,7 +1350,7 @@
   EXPECT_TRUE(FetchImage(service.get(), MakeArticleID(kSnippetUrl)).IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ExpiredContentNotRemoved) {
+TEST_F(RemoteSuggestionsProviderImplTest, ExpiredContentNotRemoved) {
   auto service = MakeSnippetsService();
 
   std::string json_str(GetTestJson({GetExpiredSnippet()}));
@@ -1467,7 +1359,7 @@
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(1));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, TestSingleSource) {
+TEST_F(RemoteSuggestionsProviderImplTest, TestSingleSource) {
   auto service = MakeSnippetsService();
 
   std::string json_str(GetTestJson({GetSnippetWithSources(
@@ -1483,7 +1375,7 @@
   EXPECT_EQ(snippet.amp_url(), GURL("http://source1.amp.com"));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, TestSingleSourceWithMalformedUrl) {
+TEST_F(RemoteSuggestionsProviderImplTest, TestSingleSourceWithMalformedUrl) {
   auto service = MakeSnippetsService();
 
   std::string json_str(GetTestJson({GetSnippetWithSources(
@@ -1493,7 +1385,7 @@
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, TestSingleSourceWithMissingData) {
+TEST_F(RemoteSuggestionsProviderImplTest, TestSingleSourceWithMissingData) {
   auto service = MakeSnippetsService();
 
   std::string json_str(
@@ -1503,7 +1395,7 @@
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, LogNumArticlesHistogram) {
+TEST_F(RemoteSuggestionsProviderImplTest, LogNumArticlesHistogram) {
   auto service = MakeSnippetsService();
 
   base::HistogramTester tester;
@@ -1580,7 +1472,7 @@
   tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 6);
 }
 
-TEST_F(RemoteSuggestionsProviderTest, DismissShouldRespectAllKnownUrls) {
+TEST_F(RemoteSuggestionsProviderImplTest, DismissShouldRespectAllKnownUrls) {
   auto service = MakeSnippetsService();
 
   const base::Time creation = GetDefaultCreationTime();
@@ -1611,7 +1503,7 @@
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, StatusChanges) {
+TEST_F(RemoteSuggestionsProviderImplTest, StatusChanges) {
   auto service = MakeSnippetsService();
 
   // Simulate user signed out
@@ -1622,7 +1514,8 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_THAT(observer().StatusForCategory(articles_category()),
               Eq(CategoryStatus::SIGNED_OUT));
-  EXPECT_THAT(RemoteSuggestionsProvider::State::DISABLED, Eq(service->state_));
+  EXPECT_THAT(RemoteSuggestionsProviderImpl::State::DISABLED,
+              Eq(service->state_));
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()),
               IsEmpty());  // No fetch should be made.
 
@@ -1636,11 +1529,11 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_THAT(observer().StatusForCategory(articles_category()),
               Eq(CategoryStatus::AVAILABLE));
-  EXPECT_THAT(RemoteSuggestionsProvider::State::READY, Eq(service->state_));
+  EXPECT_THAT(RemoteSuggestionsProviderImpl::State::READY, Eq(service->state_));
   EXPECT_FALSE(service->GetSnippetsForTesting(articles_category()).empty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ImageReturnedWithTheSameId) {
+TEST_F(RemoteSuggestionsProviderImplTest, ImageReturnedWithTheSameId) {
   auto service = MakeSnippetsService();
 
   LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
@@ -1665,7 +1558,7 @@
   EXPECT_EQ(1, image.Width());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, EmptyImageReturnedForNonExistentId) {
+TEST_F(RemoteSuggestionsProviderImplTest, EmptyImageReturnedForNonExistentId) {
   auto service = MakeSnippetsService();
 
   // Create a non-empty image so that we can test the image gets updated.
@@ -1682,7 +1575,7 @@
   EXPECT_TRUE(image.IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest,
+TEST_F(RemoteSuggestionsProviderImplTest,
        FetchingUnknownImageIdShouldNotHitDatabase) {
   // Testing that the provider is not accessing the database is tricky.
   // Therefore, we simply put in some data making sure that if the provider asks
@@ -1708,7 +1601,7 @@
   EXPECT_TRUE(image.IsEmpty()) << "got image with width: " << image.Width();
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ClearHistoryRemovesAllSuggestions) {
+TEST_F(RemoteSuggestionsProviderImplTest, ClearHistoryRemovesAllSuggestions) {
   auto service = MakeSnippetsService();
 
   std::string first_snippet = GetSnippetWithUrl("http://url1.com");
@@ -1732,7 +1625,8 @@
               IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest, SuggestionsFetchedOnSignInAndSignOut) {
+TEST_F(RemoteSuggestionsProviderImplTest,
+       SuggestionsFetchedOnSignInAndSignOut) {
   auto service = MakeSnippetsService();
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
 
@@ -1752,7 +1646,7 @@
   EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(2));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, ShouldClearOrphanedImagesOnRestart) {
+TEST_F(RemoteSuggestionsProviderImplTest, ShouldClearOrphanedImagesOnRestart) {
   auto service = MakeSnippetsService();
 
   LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
@@ -1778,7 +1672,7 @@
   EXPECT_TRUE(FetchImage(service.get(), MakeArticleID(kSnippetUrl)).IsEmpty());
 }
 
-TEST_F(RemoteSuggestionsProviderTest,
+TEST_F(RemoteSuggestionsProviderImplTest,
        ShouldHandleMoreThanMaxSnippetsInResponse) {
   auto service = MakeSnippetsService();
 
@@ -1794,10 +1688,11 @@
               SizeIs(service->GetMaxSnippetCountForTesting() + 1));
 }
 
-TEST_F(RemoteSuggestionsProviderTest, StoreLastSuccessfullBackgroundFetchTime) {
-  // On initialization of the RemoteSuggestionsProvider a background fetch is
-  // triggered since the snippets DB is empty. Therefore the service must not be
-  // initialized until the test clock is set.
+TEST_F(RemoteSuggestionsProviderImplTest,
+       StoreLastSuccessfullBackgroundFetchTime) {
+  // On initialization of the RemoteSuggestionsProviderImpl a background fetch
+  // is triggered since the snippets DB is empty. Therefore the service must not
+  // be initialized until the test clock is set.
   auto service = MakeSnippetsServiceWithoutInitialization();
 
   auto simple_test_clock = base::MakeUnique<base::SimpleTestClock>();
@@ -1817,8 +1712,10 @@
   // Advance the time and check whether the time was updated correctly after the
   // background fetch.
   simple_test_clock_ptr->Advance(TimeDelta::FromHours(1));
-  service->FetchSnippetsInTheBackground();
+
+  service->RefetchInTheBackground(/*callback=*/nullptr);
   base::RunLoop().RunUntilIdle();
+  // TODO(jkrcal): Move together with the pref storage into the scheduler.
   EXPECT_EQ(
       simple_test_clock_ptr->Now().ToInternalValue(),
       pref_service()->GetInt64(prefs::kLastSuccessfulBackgroundFetchTime));
@@ -1826,4 +1723,86 @@
   // scheduler refactoring is done (crbug.com/672434).
 }
 
+TEST_F(RemoteSuggestionsProviderImplTest, CallsProviderStatusCallbackIfReady) {
+  // Initiate the service so that it is already READY.
+  auto service = MakeSnippetsService();
+
+  StrictMock<MockFunction<void(RemoteSuggestionsProvider::ProviderStatus)>>
+      status_callback;
+  // The callback should be called on registering.
+  EXPECT_CALL(status_callback,
+              Call(RemoteSuggestionsProvider::ProviderStatus::ACTIVE));
+  service->SetProviderStatusCallback(
+      base::MakeUnique<RemoteSuggestionsProvider::ProviderStatusCallback>(
+          base::Bind(&MockFunction<void(
+                         RemoteSuggestionsProvider::ProviderStatus)>::Call,
+                     base::Unretained(&status_callback))));
+}
+
+TEST_F(RemoteSuggestionsProviderImplTest,
+       DoesNotCallProviderStatusCallbackIfNotInited) {
+  auto service = MakeSnippetsServiceWithoutInitialization();
+
+  StrictMock<MockFunction<void(RemoteSuggestionsProvider::ProviderStatus)>>
+      status_callback;
+  // The provider is not initialized yet, no callback should be called on
+  // registering.
+  service->SetProviderStatusCallback(
+      base::MakeUnique<RemoteSuggestionsProvider::ProviderStatusCallback>(
+          base::Bind(&MockFunction<void(
+                         RemoteSuggestionsProvider::ProviderStatus)>::Call,
+                     base::Unretained(&status_callback))));
+}
+
+TEST_F(RemoteSuggestionsProviderImplTest,
+       CallsProviderStatusCallbackWhenReady) {
+  auto service = MakeSnippetsServiceWithoutInitialization();
+  StrictMock<MockFunction<void(RemoteSuggestionsProvider::ProviderStatus)>>
+      status_callback;
+  service->SetProviderStatusCallback(
+      base::MakeUnique<RemoteSuggestionsProvider::ProviderStatusCallback>(
+          base::Bind(&MockFunction<void(
+                         RemoteSuggestionsProvider::ProviderStatus)>::Call,
+                     base::Unretained(&status_callback))));
+
+  // Should be called when becoming ready.
+  EXPECT_CALL(status_callback,
+              Call(RemoteSuggestionsProvider::ProviderStatus::ACTIVE));
+  WaitForSnippetsServiceInitialization(service.get(),
+                                       /*set_empty_response=*/true);
+}
+
+TEST_F(RemoteSuggestionsProviderImplTest, CallsProviderStatusCallbackOnError) {
+  auto service = MakeSnippetsServiceWithoutInitialization();
+  StrictMock<MockFunction<void(RemoteSuggestionsProvider::ProviderStatus)>>
+      status_callback;
+  service->SetProviderStatusCallback(
+      base::MakeUnique<RemoteSuggestionsProvider::ProviderStatusCallback>(
+          base::Bind(&MockFunction<void(
+                         RemoteSuggestionsProvider::ProviderStatus)>::Call,
+                     base::Unretained(&status_callback))));
+
+  // Should be called on error.
+  EXPECT_CALL(status_callback,
+              Call(RemoteSuggestionsProvider::ProviderStatus::INACTIVE));
+  service->EnterState(RemoteSuggestionsProviderImpl::State::ERROR_OCCURRED);
+}
+
+TEST_F(RemoteSuggestionsProviderImplTest,
+       CallsProviderStatusCallbackWhenDisabled) {
+  auto service = MakeSnippetsServiceWithoutInitialization();
+  StrictMock<MockFunction<void(RemoteSuggestionsProvider::ProviderStatus)>>
+      status_callback;
+  service->SetProviderStatusCallback(
+      base::MakeUnique<RemoteSuggestionsProvider::ProviderStatusCallback>(
+          base::Bind(&MockFunction<void(
+                         RemoteSuggestionsProvider::ProviderStatus)>::Call,
+                     base::Unretained(&status_callback))));
+
+  // Should be called when becoming disabled.
+  EXPECT_CALL(status_callback,
+              Call(RemoteSuggestionsProvider::ProviderStatus::INACTIVE));
+  service->EnterState(RemoteSuggestionsProviderImpl::State::DISABLED);
+}
+
 }  // namespace ntp_snippets
diff --git a/components/ntp_snippets/remote/remote_suggestions_scheduler.h b/components/ntp_snippets/remote/remote_suggestions_scheduler.h
index bad65c9..953592ae 100644
--- a/components/ntp_snippets/remote/remote_suggestions_scheduler.h
+++ b/components/ntp_snippets/remote/remote_suggestions_scheduler.h
@@ -6,56 +6,23 @@
 #define COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_SCHEDULER_H_
 
 #include "base/macros.h"
-#include "base/time/time.h"
 
 namespace ntp_snippets {
 
-// Class to take care of scheduling of periodic updates of snippets. There are
-// two types of scheduled updates:
-//  - "hard" ones that should outlive current running instance of Chrome. These
-//  should get triggered according to their schedule even if Chrome is not
-//  running at the given moment. This is OS-dependent, may be unavilable on
-//  some platforms.
-//  - "soft" ones that get triggered only if Chrome stays running until the
-//  scheduled point.
+// Interface for informing the scheduler.
 class RemoteSuggestionsScheduler {
  public:
-  // Interface to perform the scheduled update.
-  class Updater {
-    virtual void HardUpdate();
-    virtual void SoftUpdate();
-  };
+  // External triggers to consider fetching content suggestions.
+  virtual void OnBrowserStartup() = 0;
+  virtual void OnNTPOpened() = 0;
 
-  // The passed in |updater| is called when an update is due according to the
-  // schedule. Note that hard fetches get access to the |updater| via the keyed
-  // ContentSuggestionService because the concrete instance passed to
-  // RemoteSuggestionsScheduler when the hard fetch was scheduled may not exist
-  // any more when the hard update is due.
-  explicit RemoteSuggestionsScheduler(Updater* updater);
+  // Fetch content suggestions.
+  virtual void OnPersistentSchedulerWakeUp() = 0;
 
-  // Schedules both "soft" and "hard" fetches. First removes existing schedule
-  // before scheduling new updates.
-  void Schedule();
-
-  // Removes any existing schedule.
-  void Unschedule();
-
-  // Schedule periodic fetching of snippets, with different periods depending on
-  // network state. Once per period, the concrete implementation should call
-  // RemoteSuggestionsUpdater::HardUpdate where RemoteSuggestionsUpdater is
-  // obtained from ContentSuggestionsService.
-  // Any of the periods can be zero to indicate that the corresponding task
-  // should not be scheduled.
-  virtual bool Schedule(base::TimeDelta period_wifi,
-                        base::TimeDelta period_fallback) = 0;
-
-  // Cancel any scheduled tasks.
-  virtual bool Unschedule() = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(RemoteSuggestionsHardScheduler);
+  // Force rescheduling of fetching.
+  virtual void RescheduleFetching() = 0;
 };
 
 }  // namespace ntp_snippets
 
-#endif  // COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_HARD_SCHEDULER_H_
+#endif  // COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_SCHEDULER_H_
diff --git a/components/ntp_snippets/remote/scheduling_remote_suggestions_provider.cc b/components/ntp_snippets/remote/scheduling_remote_suggestions_provider.cc
new file mode 100644
index 0000000..fde80e3
--- /dev/null
+++ b/components/ntp_snippets/remote/scheduling_remote_suggestions_provider.cc
@@ -0,0 +1,337 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ntp_snippets/remote/scheduling_remote_suggestions_provider.h"
+
+#include <string>
+#include <utility>
+
+#include "components/ntp_snippets/features.h"
+#include "components/ntp_snippets/pref_names.h"
+#include "components/ntp_snippets/remote/persistent_scheduler.h"
+#include "components/ntp_snippets/status.h"
+#include "components/ntp_snippets/user_classifier.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace ntp_snippets {
+
+namespace {
+
+// Default values for fetching intervals, fallback and wifi.
+const double kDefaultFetchingIntervalRareNtpUser[] = {48.0, 24.0};
+const double kDefaultFetchingIntervalActiveNtpUser[] = {24.0, 6.0};
+const double kDefaultFetchingIntervalActiveSuggestionsConsumer[] = {24.0, 6.0};
+
+// Variation parameters than can the default fetching intervals.
+const char* kFetchingIntervalParamNameRareNtpUser[] = {
+    "fetching_interval_hours-fallback-rare_ntp_user",
+    "fetching_interval_hours-wifi-rare_ntp_user"};
+const char* kFetchingIntervalParamNameActiveNtpUser[] = {
+    "fetching_interval_hours-fallback-active_ntp_user",
+    "fetching_interval_hours-wifi-active_ntp_user"};
+const char* kFetchingIntervalParamNameActiveSuggestionsConsumer[] = {
+    "fetching_interval_hours-fallback-active_suggestions_consumer",
+    "fetching_interval_hours-wifi-active_suggestions_consumer"};
+
+base::TimeDelta GetDesiredUpdateInterval(
+    bool is_wifi,
+    UserClassifier::UserClass user_class) {
+  double default_value_hours = 0.0;
+
+  const int index = is_wifi ? 1 : 0;
+  const char* param_name = nullptr;
+  switch (user_class) {
+    case UserClassifier::UserClass::RARE_NTP_USER:
+      default_value_hours = kDefaultFetchingIntervalRareNtpUser[index];
+      param_name = kFetchingIntervalParamNameRareNtpUser[index];
+      break;
+    case UserClassifier::UserClass::ACTIVE_NTP_USER:
+      default_value_hours = kDefaultFetchingIntervalActiveNtpUser[index];
+      param_name = kFetchingIntervalParamNameActiveNtpUser[index];
+      break;
+    case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER:
+      default_value_hours =
+          kDefaultFetchingIntervalActiveSuggestionsConsumer[index];
+      param_name = kFetchingIntervalParamNameActiveSuggestionsConsumer[index];
+      break;
+  }
+
+  double value_hours = variations::GetVariationParamByFeatureAsDouble(
+      ntp_snippets::kArticleSuggestionsFeature, param_name,
+      default_value_hours);
+
+  return base::TimeDelta::FromSecondsD(value_hours * 3600.0);
+}
+
+}  // namespace
+
+struct SchedulingRemoteSuggestionsProvider::FetchingSchedule {
+  base::TimeDelta interval_wifi;
+  base::TimeDelta interval_fallback;
+
+  static FetchingSchedule Empty() {
+    return FetchingSchedule{base::TimeDelta(),
+                            base::TimeDelta()};
+  }
+
+  bool operator==(const FetchingSchedule& other) const {
+    return interval_wifi == other.interval_wifi &&
+           interval_fallback == other.interval_fallback;
+  }
+
+  bool operator!=(const FetchingSchedule& other) const {
+    return !operator==(other);
+  }
+
+  bool is_empty() const {
+    return interval_wifi.is_zero() && interval_fallback.is_zero();
+  }
+};
+
+SchedulingRemoteSuggestionsProvider::SchedulingRemoteSuggestionsProvider(
+    Observer* observer,
+    std::unique_ptr<RemoteSuggestionsProvider> provider,
+    PersistentScheduler* persistent_scheduler,
+    const UserClassifier* user_classifier,
+    PrefService* pref_service)
+    : RemoteSuggestionsProvider(observer),
+      RemoteSuggestionsScheduler(),
+      provider_(std::move(provider)),
+      persistent_scheduler_(persistent_scheduler),
+      user_classifier_(user_classifier),
+      pref_service_(pref_service) {
+  DCHECK(user_classifier);
+  DCHECK(pref_service);
+
+  provider_->SetProviderStatusCallback(
+      base::MakeUnique<RemoteSuggestionsProvider::ProviderStatusCallback>(
+          base::BindRepeating(
+              &SchedulingRemoteSuggestionsProvider::OnProviderStatusChanged,
+              base::Unretained(this))));
+}
+
+SchedulingRemoteSuggestionsProvider::~SchedulingRemoteSuggestionsProvider() =
+    default;
+
+// static
+void SchedulingRemoteSuggestionsProvider::RegisterProfilePrefs(
+    PrefRegistrySimple* registry) {
+  registry->RegisterInt64Pref(prefs::kSnippetBackgroundFetchingIntervalWifi, 0);
+  registry->RegisterInt64Pref(prefs::kSnippetBackgroundFetchingIntervalFallback,
+                              0);
+}
+
+void SchedulingRemoteSuggestionsProvider::RescheduleFetching() {
+  // Force the reschedule by stopping and starting it again.
+  StopScheduling();
+  StartScheduling();
+}
+
+void SchedulingRemoteSuggestionsProvider::OnPersistentSchedulerWakeUp() {
+  provider_->RefetchInTheBackground(
+      base::MakeUnique<RemoteSuggestionsProvider::FetchStatusCallback>(
+          base::Bind(&SchedulingRemoteSuggestionsProvider::OnFetchCompleted,
+                     base::Unretained(this))));
+}
+
+void SchedulingRemoteSuggestionsProvider::OnBrowserStartup() {
+  // TODO(jkrcal): Implement.
+}
+
+void SchedulingRemoteSuggestionsProvider::OnNTPOpened() {
+  // TODO(jkrcal): Implement.
+}
+
+void SchedulingRemoteSuggestionsProvider::SetProviderStatusCallback(
+      std::unique_ptr<ProviderStatusCallback> callback) {
+  provider_->SetProviderStatusCallback(std::move(callback));
+}
+
+void SchedulingRemoteSuggestionsProvider::RefetchInTheBackground(
+    std::unique_ptr<FetchStatusCallback> callback) {
+  provider_->RefetchInTheBackground(std::move(callback));
+}
+
+const NTPSnippetsFetcher* SchedulingRemoteSuggestionsProvider::
+    snippets_fetcher_for_testing_and_debugging() const {
+  return provider_->snippets_fetcher_for_testing_and_debugging();
+}
+
+CategoryStatus SchedulingRemoteSuggestionsProvider::GetCategoryStatus(
+    Category category) {
+  return provider_->GetCategoryStatus(category);
+}
+
+CategoryInfo SchedulingRemoteSuggestionsProvider::GetCategoryInfo(
+    Category category) {
+  return provider_->GetCategoryInfo(category);
+}
+
+void SchedulingRemoteSuggestionsProvider::DismissSuggestion(
+    const ContentSuggestion::ID& suggestion_id) {
+  provider_->DismissSuggestion(suggestion_id);
+}
+
+void SchedulingRemoteSuggestionsProvider::FetchSuggestionImage(
+    const ContentSuggestion::ID& suggestion_id,
+    const ImageFetchedCallback& callback) {
+  provider_->FetchSuggestionImage(suggestion_id, callback);
+}
+
+void SchedulingRemoteSuggestionsProvider::Fetch(
+    const Category& category,
+    const std::set<std::string>& known_suggestion_ids,
+    const FetchDoneCallback& callback) {
+  provider_->Fetch(
+      category, known_suggestion_ids,
+      base::Bind(&SchedulingRemoteSuggestionsProvider::FetchFinished,
+                 base::Unretained(this), callback));
+}
+
+void SchedulingRemoteSuggestionsProvider::ReloadSuggestions() {
+  provider_->ReloadSuggestions();
+}
+
+void SchedulingRemoteSuggestionsProvider::ClearHistory(
+    base::Time begin,
+    base::Time end,
+    const base::Callback<bool(const GURL& url)>& filter) {
+  provider_->ClearHistory(begin, end, filter);
+}
+
+void SchedulingRemoteSuggestionsProvider::ClearCachedSuggestions(
+    Category category) {
+  provider_->ClearCachedSuggestions(category);
+}
+
+void SchedulingRemoteSuggestionsProvider::OnSignInStateChanged() {
+  provider_->OnSignInStateChanged();
+}
+
+void SchedulingRemoteSuggestionsProvider::GetDismissedSuggestionsForDebugging(
+    Category category,
+    const DismissedSuggestionsCallback& callback) {
+  provider_->GetDismissedSuggestionsForDebugging(category, callback);
+}
+
+void SchedulingRemoteSuggestionsProvider::ClearDismissedSuggestionsForDebugging(
+    Category category) {
+  provider_->ClearDismissedSuggestionsForDebugging(category);
+}
+
+void SchedulingRemoteSuggestionsProvider::OnProviderStatusChanged(
+    RemoteSuggestionsProvider::ProviderStatus status) {
+  switch (status) {
+    case RemoteSuggestionsProvider::ProviderStatus::ACTIVE:
+      StartScheduling();
+      return;
+    case RemoteSuggestionsProvider::ProviderStatus::INACTIVE:
+      StopScheduling();
+      return;
+  }
+  NOTREACHED();
+}
+
+void SchedulingRemoteSuggestionsProvider::StartScheduling() {
+  // The scheduler only exists on Android so far, it's null on other platforms.
+  if (!persistent_scheduler_) {
+    return;
+  }
+
+  FetchingSchedule last_schedule = GetLastFetchingSchedule();
+  FetchingSchedule schedule = GetDesiredFetchingSchedule();
+
+  // Reset the schedule only if the parameters have changed.
+  if (last_schedule != schedule) {
+    ApplyFetchingSchedule(schedule);
+  }
+}
+
+void SchedulingRemoteSuggestionsProvider::StopScheduling() {
+  // The scheduler only exists on Android so far, it's null on other platforms.
+  if (!persistent_scheduler_) {
+    return;
+  }
+
+  // Do not unschedule if already switched off
+  FetchingSchedule last_schedule = GetLastFetchingSchedule();
+  if (last_schedule.is_empty()) {
+    return;
+  }
+
+  persistent_scheduler_->Unschedule();
+
+  StoreLastFetchingSchedule(FetchingSchedule::Empty());
+}
+
+void SchedulingRemoteSuggestionsProvider::ApplyFetchingSchedule(
+    const FetchingSchedule& schedule) {
+  persistent_scheduler_->Schedule(schedule.interval_wifi,
+                                  schedule.interval_fallback);
+
+  StoreLastFetchingSchedule(schedule);
+}
+
+SchedulingRemoteSuggestionsProvider::FetchingSchedule
+SchedulingRemoteSuggestionsProvider::GetDesiredFetchingSchedule() const {
+  UserClassifier::UserClass user_class = user_classifier_->GetUserClass();
+
+  FetchingSchedule schedule;
+  schedule.interval_wifi =
+      GetDesiredUpdateInterval(/*is_wifi=*/true, user_class);
+  schedule.interval_fallback =
+      GetDesiredUpdateInterval(/*is_wifi=*/false, user_class);
+  return schedule;
+}
+
+SchedulingRemoteSuggestionsProvider::FetchingSchedule
+SchedulingRemoteSuggestionsProvider::GetLastFetchingSchedule() const {
+  FetchingSchedule schedule;
+  schedule.interval_wifi = base::TimeDelta::FromInternalValue(
+      pref_service_->GetInt64(prefs::kSnippetBackgroundFetchingIntervalWifi));
+  schedule.interval_fallback =
+      base::TimeDelta::FromInternalValue(pref_service_->GetInt64(
+          prefs::kSnippetBackgroundFetchingIntervalFallback));
+  return schedule;
+}
+
+void SchedulingRemoteSuggestionsProvider::StoreLastFetchingSchedule(
+    const FetchingSchedule& schedule) {
+  pref_service_->SetInt64(
+      prefs::kSnippetBackgroundFetchingIntervalWifi,
+      schedule.interval_wifi.ToInternalValue());
+  pref_service_->SetInt64(
+      prefs::kSnippetBackgroundFetchingIntervalFallback,
+      schedule.interval_fallback.ToInternalValue());
+}
+
+void SchedulingRemoteSuggestionsProvider::FetchFinished(
+    const FetchDoneCallback& callback,
+    Status status_code,
+    std::vector<ContentSuggestion> suggestions) {
+  OnFetchCompleted(status_code);
+  callback.Run(status_code, std::move(suggestions));
+}
+
+void SchedulingRemoteSuggestionsProvider::OnFetchCompleted(
+    Status fetch_status) {
+  // The scheduler only exists on Android so far, it's null on other platforms.
+  if (!persistent_scheduler_) {
+    return;
+  }
+
+  if (fetch_status.code != StatusCode::SUCCESS) {
+    return;
+  }
+
+  // Reschedule after a successful fetch. This resets all currently scheduled
+  // fetches, to make sure the fallback interval triggers only if no wifi fetch
+  // succeeded, and also that we don't do a background fetch immediately after
+  // a user-initiated one.
+  ApplyFetchingSchedule(GetLastFetchingSchedule());
+}
+
+}  // namespace ntp_snippets
diff --git a/components/ntp_snippets/remote/scheduling_remote_suggestions_provider.h b/components/ntp_snippets/remote/scheduling_remote_suggestions_provider.h
new file mode 100644
index 0000000..71dd4f8
--- /dev/null
+++ b/components/ntp_snippets/remote/scheduling_remote_suggestions_provider.h
@@ -0,0 +1,149 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_SCHEDULING_REMOTE_SUGGESTIONS_PROVIDER_H_
+#define COMPONENTS_NTP_SNIPPETS_REMOTE_SCHEDULING_REMOTE_SUGGESTIONS_PROVIDER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "components/ntp_snippets/content_suggestions_provider.h"
+#include "components/ntp_snippets/remote/persistent_scheduler.h"
+#include "components/ntp_snippets/remote/remote_suggestions_provider.h"
+#include "components/ntp_snippets/remote/remote_suggestions_scheduler.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace ntp_snippets {
+
+struct Status;
+class UserClassifier;
+
+// A wrapper around RemoteSuggestionsProvider that introduces periodic fetching.
+//
+// The class initiates fetches on its own in these situations:
+//  - initial fetch when the provider is constructed and we have no suggestions;
+//  - regular fetches according to its schedule.
+// TODO(jkrcal): After soft fetch on Chrome startup is introduced, remove
+// the initial fetch completely.
+//
+// The class also needs to understand when last fetch trials and successful
+// fetches happen and thus it intercepts following interactive fetch requests:
+//  - Fetch() - after "More" button of a remote section is pressed in the UI;
+// TODO(jkrcal): Clarify what Fetch() should do for this provider and maybe stop
+// intercepting it.
+// TODO(jkrcal): Intercept also ReloadSuggestions() call (after the user swipes
+// away everything incl. all empty sections and presses "More"); Not done in the
+// first shot because  it implements a public interface function without any
+// callback.
+// This class is final because it does things in its constructor which make it
+// unsafe to derive from it.
+// TODO(jkrcal): Introduce two-phase initialization and make the class not
+// final? (see the same comment for RemoteSuggestionsProvider)
+class SchedulingRemoteSuggestionsProvider final
+    : public RemoteSuggestionsProvider,
+      public RemoteSuggestionsScheduler {
+ public:
+  SchedulingRemoteSuggestionsProvider(
+      Observer* observer,
+      std::unique_ptr<RemoteSuggestionsProvider> provider,
+      PersistentScheduler* persistent_scheduler,
+      const UserClassifier* user_classifier,
+      PrefService* pref_service);
+
+  ~SchedulingRemoteSuggestionsProvider() override;
+
+  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+  // RemoteSuggestionsScheduler implementation.
+  void RescheduleFetching() override;
+  void OnPersistentSchedulerWakeUp() override;
+  void OnBrowserStartup() override;
+  void OnNTPOpened() override;
+
+  // RemoteSuggestionsProvider implementation.
+  void SetProviderStatusCallback(
+      std::unique_ptr<ProviderStatusCallback> callback) override;
+  void RefetchInTheBackground(
+      std::unique_ptr<FetchStatusCallback> callback) override;
+  const NTPSnippetsFetcher* snippets_fetcher_for_testing_and_debugging()
+      const override;
+
+  // ContentSuggestionsProvider implementation.
+  CategoryStatus GetCategoryStatus(Category category) override;
+  CategoryInfo GetCategoryInfo(Category category) override;
+  void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
+  void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
+                            const ImageFetchedCallback& callback) override;
+  void Fetch(const Category& category,
+             const std::set<std::string>& known_suggestion_ids,
+             const FetchDoneCallback& callback) override;
+  void ReloadSuggestions() override;
+  void ClearHistory(
+      base::Time begin,
+      base::Time end,
+      const base::Callback<bool(const GURL& url)>& filter) override;
+  void ClearCachedSuggestions(Category category) override;
+  void OnSignInStateChanged() override;
+  void GetDismissedSuggestionsForDebugging(
+      Category category,
+      const DismissedSuggestionsCallback& callback) override;
+  void ClearDismissedSuggestionsForDebugging(Category category) override;
+
+ private:
+  // Abstract description of the fetching schedule.
+  struct FetchingSchedule;
+
+  // Callback that is notified whenever the status of |provider_| changes.
+  void OnProviderStatusChanged(
+      RemoteSuggestionsProvider::ProviderStatus status);
+
+  // After the call, updates will be scheduled in the future. Idempotent, can be
+  // run any time later without impacting the current schedule.
+  // If you want to enforce rescheduling, call Unschedule() and then Schedule().
+  void StartScheduling();
+
+  // After the call, no updates will happen before another call to Schedule().
+  // Idempotent, can be run any time later without impacting the current
+  // schedule.
+  void StopScheduling();
+
+  // Callback after Fetch is completed.
+  void FetchFinished(const FetchDoneCallback& callback,
+                     Status status_code,
+                     std::vector<ContentSuggestion> suggestions);
+
+  FetchingSchedule GetDesiredFetchingSchedule() const;
+  FetchingSchedule GetLastFetchingSchedule() const;
+  void StoreLastFetchingSchedule(const FetchingSchedule& schedule);
+
+  // Common function to call after each fetch.
+  void OnFetchCompleted(Status status);
+
+  // Applies the provided |schedule|.
+  void ApplyFetchingSchedule(const FetchingSchedule& schedule);
+
+  // Interface for doing all the actual work (apart from scheduling).
+  std::unique_ptr<RemoteSuggestionsProvider> provider_;
+
+  // Interface for scheduling hard fetches, OS dependent. Not owned, may be
+  // null.
+  PersistentScheduler* persistent_scheduler_;
+
+  // Used to adapt the schedule based on usage activity of the user. Not owned.
+  const UserClassifier* user_classifier_;
+
+  PrefService* pref_service_;
+
+  DISALLOW_COPY_AND_ASSIGN(SchedulingRemoteSuggestionsProvider);
+};
+
+}  // namespace ntp_snippets
+
+#endif  // COMPONENTS_NTP_SNIPPETS_REMOTE_SCHEDULING_REMOTE_SUGGESTIONS_PROVIDER_H_
diff --git a/components/ntp_snippets/remote/scheduling_remote_suggestions_provider_unittest.cc b/components/ntp_snippets/remote/scheduling_remote_suggestions_provider_unittest.cc
new file mode 100644
index 0000000..0015c83
--- /dev/null
+++ b/components/ntp_snippets/remote/scheduling_remote_suggestions_provider_unittest.cc
@@ -0,0 +1,296 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ntp_snippets/remote/scheduling_remote_suggestions_provider.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/ntp_snippets/features.h"
+#include "components/ntp_snippets/ntp_snippets_constants.h"
+#include "components/ntp_snippets/pref_names.h"
+#include "components/ntp_snippets/remote/persistent_scheduler.h"
+#include "components/ntp_snippets/remote/remote_suggestions_provider.h"
+#include "components/ntp_snippets/remote/test_utils.h"
+#include "components/ntp_snippets/status.h"
+#include "components/ntp_snippets/user_classifier.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/variations_params_manager.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::ElementsAre;
+using testing::Eq;
+using testing::InSequence;
+using testing::Invoke;
+using testing::IsEmpty;
+using testing::Mock;
+using testing::MockFunction;
+using testing::Not;
+using testing::SaveArg;
+using testing::SaveArgPointee;
+using testing::SizeIs;
+using testing::StartsWith;
+using testing::StrictMock;
+using testing::WithArgs;
+using testing::_;
+
+namespace ntp_snippets {
+
+class NTPSnippetsFetcher;
+
+namespace {
+
+class MockPersistentScheduler : public PersistentScheduler {
+ public:
+  MOCK_METHOD2(Schedule,
+               bool(base::TimeDelta period_wifi,
+                    base::TimeDelta period_fallback));
+  MOCK_METHOD0(Unschedule, bool());
+};
+
+// TODO(jkrcal): Move into its own library to reuse in other unit-tests?
+class MockRemoteSuggestionsProvider : public RemoteSuggestionsProvider {
+ public:
+  MockRemoteSuggestionsProvider(Observer* observer)
+      : RemoteSuggestionsProvider(observer) {}
+
+  // Move-only params are not supported by GMock. We want to mock out
+  // RefetchInTheBackground() which takes a unique_ptr<>. Instead, we add a new
+  // mock function which takes a copy of the callback and override the
+  // RemoteSuggestionsProvider's method to forward the call into the new mock
+  // function.
+  void SetProviderStatusCallback(
+      std::unique_ptr<RemoteSuggestionsProvider::ProviderStatusCallback>
+          callback) override {
+    SetProviderStatusCallback(*callback);
+  }
+  MOCK_METHOD1(SetProviderStatusCallback,
+               void(RemoteSuggestionsProvider::ProviderStatusCallback));
+
+  // Move-only params are not supported by GMock (same work-around as above).
+  void RefetchInTheBackground(
+      std::unique_ptr<RemoteSuggestionsProvider::FetchStatusCallback> callback)
+      override {
+    RefetchInTheBackground(*callback);
+  }
+  MOCK_METHOD1(RefetchInTheBackground,
+               void(RemoteSuggestionsProvider::FetchStatusCallback));
+
+  MOCK_CONST_METHOD0(snippets_fetcher_for_testing_and_debugging,
+                     const NTPSnippetsFetcher*());
+
+  MOCK_METHOD1(GetCategoryStatus, CategoryStatus(Category));
+  MOCK_METHOD1(GetCategoryInfo, CategoryInfo(Category));
+  MOCK_METHOD3(ClearHistory,
+               void(base::Time begin,
+                    base::Time end,
+                    const base::Callback<bool(const GURL& url)>& filter));
+  MOCK_METHOD3(Fetch,
+               void(const Category&,
+                    const std::set<std::string>&,
+                    const FetchDoneCallback&));
+  MOCK_METHOD1(ClearCachedSuggestions, void(Category));
+  MOCK_METHOD1(ClearDismissedSuggestionsForDebugging, void(Category));
+  MOCK_METHOD1(DismissSuggestion, void(const ContentSuggestion::ID&));
+  MOCK_METHOD2(FetchSuggestionImage,
+               void(const ContentSuggestion::ID&, const ImageFetchedCallback&));
+  MOCK_METHOD2(GetDismissedSuggestionsForDebugging,
+               void(Category, const DismissedSuggestionsCallback&));
+  MOCK_METHOD0(OnSignInStateChanged, void());
+};
+
+}  // namespace
+
+class SchedulingRemoteSuggestionsProviderTest
+    : public ::testing::Test {
+ public:
+  SchedulingRemoteSuggestionsProviderTest()
+      : underlying_provider_(nullptr),
+        scheduling_provider_(nullptr),
+        user_classifier_(/*pref_service=*/nullptr) {
+    SchedulingRemoteSuggestionsProvider::RegisterProfilePrefs(
+        utils_.pref_service()->registry());
+
+    auto underlying_provider =
+        base::MakeUnique<StrictMock<MockRemoteSuggestionsProvider>>(
+            /*observer=*/nullptr);
+    underlying_provider_ = underlying_provider.get();
+
+    // SchedulingRemoteSuggestionsProvider calls SetProviderStatusCallback(_) to
+    // stay in the loop of status changes.
+    EXPECT_CALL(*underlying_provider_, SetProviderStatusCallback(_))
+        .WillOnce(SaveArg<0>(&provider_status_callback_));
+
+    scheduling_provider_ =
+        base::MakeUnique<SchedulingRemoteSuggestionsProvider>(
+            /*observer=*/nullptr, std::move(underlying_provider),
+            &persistent_scheduler_, &user_classifier_, utils_.pref_service());
+  }
+
+ protected:
+  StrictMock<MockPersistentScheduler> persistent_scheduler_;
+  StrictMock<MockRemoteSuggestionsProvider>* underlying_provider_;
+  std::unique_ptr<SchedulingRemoteSuggestionsProvider> scheduling_provider_;
+  RemoteSuggestionsProvider::ProviderStatusCallback provider_status_callback_;
+
+  void ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus new_status) {
+    provider_status_callback_.Run(new_status);
+  }
+
+ private:
+  test::RemoteSuggestionsTestUtils utils_;
+  UserClassifier user_classifier_;
+
+  DISALLOW_COPY_AND_ASSIGN(SchedulingRemoteSuggestionsProviderTest);
+};
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest,
+       ShouldFetchOnPersistentSchedulerWakeUp) {
+  EXPECT_CALL(*underlying_provider_, RefetchInTheBackground(_));
+  scheduling_provider_->OnPersistentSchedulerWakeUp();
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest,
+       ShouldRescheduleOnRescheduleFetching) {
+  EXPECT_CALL(persistent_scheduler_, Schedule(_, _));
+  scheduling_provider_->RescheduleFetching();
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest, ShouldScheduleOnActivation) {
+  EXPECT_CALL(persistent_scheduler_, Schedule(_, _));
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest,
+       ShouldUnscheduleOnLaterInactivation) {
+  {
+    InSequence s;
+    EXPECT_CALL(persistent_scheduler_, Schedule(_, _));
+    EXPECT_CALL(persistent_scheduler_, Unschedule());
+  }
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::INACTIVE);
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest,
+       ShouldScheduleOnLaterActivation) {
+  EXPECT_CALL(persistent_scheduler_, Schedule(_, _));
+  // There is no schedule yet, so inactivation does not trigger unschedule.
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::INACTIVE);
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest,
+       ShouldRescheduleAfterSuccessfulFetch) {
+  // First reschedule on becoming active.
+  EXPECT_CALL(persistent_scheduler_, Schedule(_, _)).Times(2);
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+
+  RemoteSuggestionsProvider::FetchStatusCallback signal_fetch_done;
+  EXPECT_CALL(*underlying_provider_, RefetchInTheBackground(_))
+      .WillOnce(SaveArg<0>(&signal_fetch_done));
+
+  // Trigger a fetch.
+  scheduling_provider_->OnPersistentSchedulerWakeUp();
+  // Second reschedule after a successful fetch.
+  signal_fetch_done.Run(Status::Success());
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest,
+       ShouldNotRescheduleAfterFailedFetch) {
+  // Only reschedule on becoming active.
+  EXPECT_CALL(persistent_scheduler_, Schedule(_, _));
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+
+  RemoteSuggestionsProvider::FetchStatusCallback signal_fetch_done;
+  EXPECT_CALL(*underlying_provider_, RefetchInTheBackground(_))
+      .WillOnce(SaveArg<0>(&signal_fetch_done));
+
+  // Trigger a fetch.
+  scheduling_provider_->OnPersistentSchedulerWakeUp();
+  // No furter reschedule after a failure.
+  signal_fetch_done.Run(Status(StatusCode::PERMANENT_ERROR, ""));
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest, ShouldScheduleOnlyOnce) {
+  EXPECT_CALL(persistent_scheduler_, Schedule(_, _));
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+  // No further call to Schedule on a second status callback.
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest, ShouldUnscheduleOnlyOnce) {
+  {
+    InSequence s;
+    EXPECT_CALL(persistent_scheduler_, Schedule(_, _));
+    EXPECT_CALL(persistent_scheduler_, Unschedule());
+  }
+  // First schedule so that later we really unschedule.
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::INACTIVE);
+  // No further call to Unschedule on second status callback.
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::INACTIVE);
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest,
+       ReschedulesWhenWifiParamChanges) {
+  EXPECT_CALL(persistent_scheduler_, Schedule(_, _)).Times(2);
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+
+  // UserClassifier defaults to UserClass::ACTIVE_NTP_USER if PrefService is
+  // null. Change the wifi interval for this class.
+  variations::testing::VariationParamsManager params_manager(
+      ntp_snippets::kStudyName,
+      {{"fetching_interval_hours-wifi-active_ntp_user", "2"}},
+      {kArticleSuggestionsFeature.name});
+
+  // Schedule() should get called for the second time after params have changed.
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+}
+
+TEST_F(SchedulingRemoteSuggestionsProviderTest,
+       ReschedulesWhenFallbackParamChanges) {
+  EXPECT_CALL(persistent_scheduler_, Schedule(_, _)).Times(2);
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+
+  // UserClassifier defaults to UserClass::ACTIVE_NTP_USER if PrefService is
+  // null. Change the wifi interval for this class.
+  variations::testing::VariationParamsManager params_manager(
+      ntp_snippets::kStudyName,
+      {{"fetching_interval_hours-fallback-active_ntp_user", "2"}},
+      {kArticleSuggestionsFeature.name});
+
+  // Schedule() should get called for the second time after params have changed.
+  ChangeStatusOfUnderlyingProvider(
+      RemoteSuggestionsProvider::ProviderStatus::ACTIVE);
+}
+
+}  // namespace ntp_snippets
diff --git a/components/search_engines/template_url_service.cc b/components/search_engines/template_url_service.cc
index 13d8c697..2c88e36 100644
--- a/components/search_engines/template_url_service.cc
+++ b/components/search_engines/template_url_service.cc
@@ -1860,32 +1860,49 @@
     return;
   }
 
+  // Prepare the queue of TemplateURLs which must be updated. We cannot directly
+  // iterate through template_urls_ while we're updating because sometimes we
+  // want to remove TemplateURL.
+  std::set<TemplateURL*> turls_to_update;
+  for (const auto& turl : template_urls_)
+    turls_to_update.insert(turl.get());
+
   KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get());
   bool something_changed = false;
-  for (const auto& turl : template_urls_) {
+  while (!turls_to_update.empty()) {
+    auto it = turls_to_update.begin();
+    TemplateURL* turl = *it;
+    turls_to_update.erase(it);
     if (turl->HasGoogleBaseURLs(search_terms_data())) {
       TemplateURL updated_turl(turl->data());
       updated_turl.ResetKeywordIfNecessary(search_terms_data(), false);
       KeywordToTURLAndMeaningfulLength::const_iterator existing_entry =
           keyword_to_turl_and_length_.find(updated_turl.keyword());
-      if ((existing_entry != keyword_to_turl_and_length_.end()) &&
-          (existing_entry->second.first != turl.get())) {
-        // The new autogenerated keyword conflicts with another TemplateURL.
-        // Overwrite it if it's replaceable; otherwise, leave |turl| using its
-        // current keyword.  (This will not prevent |turl| from auto-updating
-        // the keyword in the future if the conflicting TemplateURL disappears.)
-        // Note that we must still update |turl| in this case, or the
-        // |provider_map_| will not be updated correctly.
-        if (CanReplace(existing_entry->second.first))
-          RemoveNoNotify(existing_entry->second.first);
-        else
-          updated_turl.data_.SetKeyword(turl->keyword());
+      if (existing_entry != keyword_to_turl_and_length_.end()) {
+        TemplateURL* existing_turl = existing_entry->second.first;
+        if (existing_turl != turl) {
+          // The new autogenerated keyword conflicts with another TemplateURL.
+          // Overwrite it if it's replaceable; otherwise, leave |turl| using its
+          // current keyword.  (This will not prevent |turl| from auto-updating
+          // the keyword in the future if the conflicting TemplateURL
+          // disappears.) Note that we must still update |turl| in this case, or
+          // the |provider_map_| will not be updated correctly.
+          if (CanReplace(existing_turl)) {
+            RemoveNoNotify(existing_turl);
+            // Remove |existing_url| from the queue we're iterating through.
+            // Perhaps there is no |existing_url| in this queue already if this
+            // cycle processed |existing_url| before |turl|.
+            turls_to_update.erase(existing_turl);
+          } else {
+            updated_turl.data_.SetKeyword(turl->keyword());
+          }
+        }
       }
       something_changed = true;
       // This will send the keyword change to sync.  Note that other clients
       // need to reset the keyword to an appropriate local value when this
       // change arrives; see CreateTemplateURLFromTemplateURLAndSyncData().
-      UpdateNoNotify(turl.get(), updated_turl);
+      UpdateNoNotify(turl, updated_turl);
     }
   }
   if (something_changed)
diff --git a/components/signin/core/account_id/account_id.cc b/components/signin/core/account_id/account_id.cc
index 00ceb2e2..a795976 100644
--- a/components/signin/core/account_id/account_id.cc
+++ b/components/signin/core/account_id/account_id.cc
@@ -16,25 +16,19 @@
 
 namespace {
 
-// Known account types.
-const char kGoogle[] = "google";
-
 // Serialization keys
 const char kGaiaIdKey[] = "gaia_id";
 const char kEmailKey[] = "email";
+const char kObjGuid[] = "obj_guid";
+const char kAccountTypeKey[] = "account_type";
+
+// Serialization values for account type.
+const std::string kGoogle = "google";
+const std::string kAd = "ad";
 
 // Prefix for GetAccountIdKey().
 const char kKeyGaiaIdPrefix[] = "g-";
-
-struct GoogleStringSingleton {
-  GoogleStringSingleton() : google(kGoogle) {}
-
-  static GoogleStringSingleton* GetInstance() {
-    return base::Singleton<GoogleStringSingleton>::get();
-  }
-
-  const std::string google;
-};
+const char kKeyAdIdPrefix[] = "a-";
 
 }  // anonymous namespace
 
@@ -49,26 +43,46 @@
 
 AccountId::AccountId() {}
 
-AccountId::AccountId(const std::string& gaia_id, const std::string& user_email)
-    : gaia_id_(gaia_id), user_email_(user_email) {
+AccountId::AccountId(const std::string& id,
+                     const std::string& user_email,
+                     const AccountType& account_type)
+    : id_(id), user_email_(user_email), account_type_(account_type) {
+  DCHECK(account_type != AccountType::UNKNOWN || id.empty());
+  DCHECK(account_type != AccountType::ACTIVE_DIRECTORY || !id.empty());
   // Fail if e-mail looks similar to GaiaIdKey.
   LOG_ASSERT(!base::StartsWith(user_email, kKeyGaiaIdPrefix,
                                base::CompareCase::SENSITIVE) ||
              user_email.find('@') != std::string::npos)
-      << "Bad e-mail: '" << user_email << "' with gaia_id='" << gaia_id << "'";
+      << "Bad e-mail: '" << user_email << "' with gaia_id='" << id << "'";
 
   // TODO(alemate): DCHECK(!email.empty());
   // TODO(alemate): check gaia_id is not empty once it is required.
 }
 
 AccountId::AccountId(const AccountId& other)
-    : gaia_id_(other.gaia_id_), user_email_(other.user_email_) {}
+    : id_(other.id_),
+      user_email_(other.user_email_),
+      account_type_(other.account_type_) {}
 
 bool AccountId::operator==(const AccountId& other) const {
-  return (this == &other) ||
-         (gaia_id_ == other.gaia_id_ && user_email_ == other.user_email_) ||
-         (!gaia_id_.empty() && gaia_id_ == other.gaia_id_) ||
-         (!user_email_.empty() && user_email_ == other.user_email_);
+  if (this == &other)
+    return true;
+  if (account_type_ == AccountType::UNKNOWN ||
+      other.account_type_ == AccountType::UNKNOWN)
+    return user_email_ == other.user_email_;
+  if (account_type_ != other.account_type_)
+    return false;
+  switch (account_type_) {
+    case AccountType::GOOGLE:
+      return (id_ == other.id_ && user_email_ == other.user_email_) ||
+             (!id_.empty() && id_ == other.id_) ||
+             (!user_email_.empty() && user_email_ == other.user_email_);
+    case AccountType::ACTIVE_DIRECTORY:
+      return id_ == other.id_ && user_email_ == other.user_email_;
+    default:
+      NOTREACHED() << "Unknown account type";
+  }
+  return false;
 }
 
 bool AccountId::operator!=(const AccountId& other) const {
@@ -81,45 +95,70 @@
 }
 
 bool AccountId::empty() const {
-  return gaia_id_.empty() && user_email_.empty();
+  return id_.empty() && user_email_.empty() &&
+         account_type_ == AccountType::UNKNOWN;
 }
 
 bool AccountId::is_valid() const {
-  return /* !gaia_id_.empty() && */ !user_email_.empty();
+  switch (account_type_) {
+    case AccountType::GOOGLE:
+      return /* !id_.empty() && */ !user_email_.empty();
+    case AccountType::ACTIVE_DIRECTORY:
+      return !id_.empty() && !user_email_.empty();
+    case AccountType::UNKNOWN:
+      return id_.empty() && !user_email_.empty();
+  }
+  NOTREACHED();
+  return false;
 }
 
 void AccountId::clear() {
-  gaia_id_.clear();
+  id_.clear();
   user_email_.clear();
+  account_type_ = AccountType::UNKNOWN;
 }
 
-const std::string& AccountId::GetAccountType() const {
-  return GoogleStringSingleton::GetInstance()->google;
+AccountType AccountId::GetAccountType() const {
+  return account_type_;
 }
 
 const std::string& AccountId::GetGaiaId() const {
-  return gaia_id_;
+  if (account_type_ != AccountType::GOOGLE)
+    NOTIMPLEMENTED() << "Failed to get gaia_id for non-Google account.";
+  return id_;
+}
+
+const std::string& AccountId::GetObjGuid() const {
+  if (account_type_ != AccountType::ACTIVE_DIRECTORY)
+    NOTIMPLEMENTED()
+        << "Failed to get obj_guid for non-Active Directory account.";
+  return id_;
 }
 
 const std::string& AccountId::GetUserEmail() const {
   return user_email_;
 }
 
-const std::string AccountId::GetAccountIdKey() const {
-#ifdef NDEBUG
-  if (gaia_id_.empty())
-    LOG(FATAL) << "GetAccountIdKey(): no gaia id for " << Serialize();
-
-#else
-  CHECK(!gaia_id_.empty());
-#endif
-
-  return std::string(kKeyGaiaIdPrefix) + gaia_id_;
+bool AccountId::HasAccountIdKey() const {
+  return account_type_ != AccountType::UNKNOWN && !id_.empty();
 }
 
-void AccountId::SetGaiaId(const std::string& gaia_id) {
-  DCHECK(!gaia_id.empty());
-  gaia_id_ = gaia_id;
+const std::string AccountId::GetAccountIdKey() const {
+#ifdef NDEBUG
+  if (id_.empty())
+    LOG(FATAL) << "GetAccountIdKey(): no id for " << Serialize();
+#else
+  CHECK(!id_.empty());
+#endif
+  switch (GetAccountType()) {
+    case AccountType::GOOGLE:
+      return std::string(kKeyGaiaIdPrefix) + id_;
+    case AccountType::ACTIVE_DIRECTORY:
+      return std::string(kKeyAdIdPrefix) + id_;
+    default:
+      NOTREACHED() << "Unknown account type";
+  }
+  return std::string();
 }
 
 void AccountId::SetUserEmail(const std::string& email) {
@@ -130,24 +169,74 @@
 // static
 AccountId AccountId::FromUserEmail(const std::string& email) {
   // TODO(alemate): DCHECK(!email.empty());
-  return AccountId(std::string() /* gaia_id */, email);
+  return AccountId(std::string() /* id */, email, AccountType::UNKNOWN);
 }
 
+// static
 AccountId AccountId::FromGaiaId(const std::string& gaia_id) {
   DCHECK(!gaia_id.empty());
-  return AccountId(gaia_id, std::string() /* email */);
+  return AccountId(gaia_id, std::string() /* email */, AccountType::GOOGLE);
 }
 
 // static
 AccountId AccountId::FromUserEmailGaiaId(const std::string& email,
                                          const std::string& gaia_id) {
   DCHECK(!(email.empty() && gaia_id.empty()));
-  return AccountId(gaia_id, email);
+  return AccountId(gaia_id, email, AccountType::GOOGLE);
+}
+
+// static
+AccountId AccountId::AdFromUserEmailObjGuid(const std::string& email,
+                                            const std::string& obj_guid) {
+  DCHECK(!email.empty() && !obj_guid.empty());
+  return AccountId(obj_guid, email, AccountType::ACTIVE_DIRECTORY);
+}
+
+// static
+AccountId AccountId::AdFromObjGuid(const std::string& obj_guid) {
+  DCHECK(!obj_guid.empty());
+  return AccountId(obj_guid, std::string() /* email */,
+                   AccountType::ACTIVE_DIRECTORY);
+}
+
+// static
+AccountType AccountId::StringToAccountType(
+    const std::string& account_type_string) {
+  if (account_type_string == kGoogle)
+    return AccountType::GOOGLE;
+  if (account_type_string == kAd)
+    return AccountType::ACTIVE_DIRECTORY;
+  NOTREACHED() << "Unknown account type " << account_type_string;
+  return AccountType::UNKNOWN;
+}
+
+// static
+std::string AccountId::AccountTypeToString(const AccountType& account_type) {
+  switch (account_type) {
+    case AccountType::GOOGLE:
+      return kGoogle;
+    case AccountType::ACTIVE_DIRECTORY:
+      return kAd;
+    default:
+      NOTREACHED() << "Unknown account type";
+  }
+  return std::string();
 }
 
 std::string AccountId::Serialize() const {
   base::DictionaryValue value;
-  value.SetString(kGaiaIdKey, gaia_id_);
+  switch (GetAccountType()) {
+    case AccountType::UNKNOWN:
+    case AccountType::GOOGLE:
+      value.SetString(kGaiaIdKey, id_);
+      value.SetString(kAccountTypeKey,
+                      AccountTypeToString(AccountType::GOOGLE));
+      break;
+    case AccountType::ACTIVE_DIRECTORY:
+      value.SetString(kObjGuid, id_);
+      value.SetString(kAccountTypeKey, AccountTypeToString(GetAccountType()));
+      break;
+  }
   value.SetString(kEmailKey, user_email_);
 
   std::string serialized;
@@ -167,23 +256,62 @@
 
   std::string gaia_id;
   std::string user_email;
+  std::string obj_guid;
+  std::string account_type_string;
+  AccountType account_type = AccountType::GOOGLE;
 
   const bool found_gaia_id = dictionary_value->GetString(kGaiaIdKey, &gaia_id);
   const bool found_user_email =
       dictionary_value->GetString(kEmailKey, &user_email);
+  const bool found_obj_guid = dictionary_value->GetString(kObjGuid, &obj_guid);
+  const bool found_account_type =
+      dictionary_value->GetString(kAccountTypeKey, &account_type_string);
+  if (found_account_type)
+    account_type = StringToAccountType(account_type_string);
 
-  if (!found_gaia_id)
-    LOG(ERROR) << "gaia_id is not found in '" << serialized << "'";
+  switch (account_type) {
+    case AccountType::GOOGLE:
+      if (found_obj_guid)
+        LOG(ERROR) << "AccountType is 'google' but obj_guid is found in '"
+                   << serialized << "'";
 
-  if (!found_user_email)
-    LOG(ERROR) << "user_email is not found in '" << serialized << "'";
+      if (!found_gaia_id)
+        LOG(ERROR) << "gaia_id is not found in '" << serialized << "'";
 
-  if (!found_gaia_id && !found_user_email)
-    return false;
+      if (!found_user_email)
+        LOG(ERROR) << "user_email is not found in '" << serialized << "'";
 
-  *account_id = FromUserEmailGaiaId(user_email, gaia_id);
+      if (!found_gaia_id && !found_user_email)
+        return false;
 
-  return true;
+      *account_id = FromUserEmailGaiaId(user_email, gaia_id);
+      return true;
+
+    case AccountType::ACTIVE_DIRECTORY:
+      if (found_gaia_id)
+        LOG(ERROR)
+            << "AccountType is 'active directory' but gaia_id is found in '"
+            << serialized << "'";
+
+      if (!found_obj_guid) {
+        LOG(ERROR) << "obj_guid is not found in '" << serialized << "'";
+        return false;
+      }
+
+      if (!found_user_email) {
+        LOG(ERROR) << "user_email is not found in '" << serialized << "'";
+      }
+
+      if (!found_obj_guid || !found_user_email)
+        return false;
+
+      *account_id = AdFromUserEmailObjGuid(user_email, obj_guid);
+      return true;
+
+    default:
+      NOTREACHED() << "Unknown account type";
+      return false;
+  }
 }
 
 const AccountId& EmptyAccountId() {
diff --git a/components/signin/core/account_id/account_id.h b/components/signin/core/account_id/account_id.h
index 7cba697..4bc0205 100644
--- a/components/signin/core/account_id/account_id.h
+++ b/components/signin/core/account_id/account_id.h
@@ -10,18 +10,28 @@
 #include <string>
 #include "base/containers/hash_tables.h"
 
+enum class AccountType { UNKNOWN, GOOGLE, ACTIVE_DIRECTORY };
+
 // Type that contains enough information to identify user.
 //
 // TODO(alemate): we are in the process of moving away from std::string as a
 // type for storing user identifier to AccountId. At this time GaiaId is mostly
 // empty, so this type is used as a replacement for e-mail string.
 // But in near future AccountId will become full feature user identifier.
+// TODO(alemate): Rename functions and fields to reflect different types of
+// accounts. (see crbug.com/672253)
 class AccountId {
  public:
   struct EmptyAccountId;
 
   AccountId(const AccountId& other);
 
+  // If any of the comparable AccountIds has AccountType == UNKNOWN then it
+  // compares emails.
+  // If both are not UNKNOWN and not equal then it returns false.
+  // If AccountType == GOOGLE then it checks if either ids or emails are equal.
+  // If AccountType == ACTIVE_DIRECTORY then it checks if ids and emails are
+  // equal.
   bool operator==(const AccountId& other) const;
   bool operator!=(const AccountId& other) const;
   bool operator<(const AccountId& right) const;
@@ -31,28 +41,45 @@
   bool is_valid() const;
   void clear();
 
-  const std::string& GetAccountType() const;
+  AccountType GetAccountType() const;
   const std::string& GetGaiaId() const;
+  const std::string& GetObjGuid() const;
   // Users of AccountId should make no assumptions on the format of email.
   // I.e. it cannot be used as account identifier, because it is (in general)
   // non-comparable.
   const std::string& GetUserEmail() const;
 
+  // Returns true if |GetAccountIdKey| would return valid key.
+  bool HasAccountIdKey() const;
   // This returns prefixed some string that can be used as a storage key.
   // You should make no assumptions on the format of this string.
   const std::string GetAccountIdKey() const;
 
-  void SetGaiaId(const std::string& gaia_id);
   void SetUserEmail(const std::string& email);
 
   // This method is to be used during transition period only.
+  // AccountId with UNKNOWN AccountType;
   static AccountId FromUserEmail(const std::string& user_email);
+  // AccountId with GOOGLE AccountType;
   // This method is to be used during transition period only.
   static AccountId FromGaiaId(const std::string& gaia_id);
   // This method is the preferred way to construct AccountId if you have
   // full account information.
+  // AccountId with GOOGLE AccountType;
   static AccountId FromUserEmailGaiaId(const std::string& user_email,
                                        const std::string& gaia_id);
+  // These methods are used to construct Active Directory AccountIds.
+  // AccountId with ACTIVE_DIRECTORY AccountType;
+  static AccountId AdFromUserEmailObjGuid(const std::string& email,
+                                          const std::string& obj_guid);
+  // AccountId with ACTIVE_DIRECTORY AccountType;
+  static AccountId AdFromObjGuid(const std::string& obj_guid);
+
+  // Translation functions between AccountType and std::string. Used for
+  // serialization.
+  static AccountType StringToAccountType(
+      const std::string& account_type_string);
+  static std::string AccountTypeToString(const AccountType& account_type);
 
   // These are (for now) unstable and cannot be used to store serialized data to
   // persistent storage. Only in-memory storage is safe.
@@ -64,10 +91,13 @@
 
  private:
   AccountId();
-  AccountId(const std::string& gaia_id, const std::string& user_email);
+  AccountId(const std::string& id,
+            const std::string& user_email,
+            const AccountType& account_type);
 
-  std::string gaia_id_;
+  std::string id_;
   std::string user_email_;
+  AccountType account_type_ = AccountType::UNKNOWN;
 };
 
 // Returns a reference to a singleton.
diff --git a/components/translate/ios/browser/language_detection_controller.mm b/components/translate/ios/browser/language_detection_controller.mm
index ed82a83..14c6800 100644
--- a/components/translate/ios/browser/language_detection_controller.mm
+++ b/components/translate/ios/browser/language_detection_controller.mm
@@ -87,8 +87,8 @@
     return false;
   }
 
-  int capture_text_time = 0;
-  command.GetInteger("captureTextTime", &capture_text_time);
+  double capture_text_time = 0;
+  command.GetDouble("captureTextTime", &capture_text_time);
   UMA_HISTOGRAM_TIMES(kTranslateCaptureText,
                       base::TimeDelta::FromMillisecondsD(capture_text_time));
   std::string html_lang;
diff --git a/components/user_manager/known_user.cc b/components/user_manager/known_user.cc
index 8ad90614..815d105e 100644
--- a/components/user_manager/known_user.cc
+++ b/components/user_manager/known_user.cc
@@ -32,6 +32,12 @@
 // Key of obfuscated GAIA id value.
 const char kGAIAIdKey[] = "gaia_id";
 
+// Key of obfuscated object guid value for Active Directory accounts.
+const char kObjGuidKey[] = "obj_guid";
+
+// Key of account type.
+const char kAccountTypeKey[] = "account_type";
+
 // Key of whether this user ID refers to a SAML user.
 const char kUsingSAMLKey[] = "using_saml";
 
@@ -58,11 +64,29 @@
 bool UserMatches(const AccountId& account_id,
                  const base::DictionaryValue& dict) {
   std::string value;
+  if (account_id.GetAccountType() != AccountType::UNKNOWN &&
+      dict.GetString(kAccountTypeKey, &value) &&
+      account_id.GetAccountType() != AccountId::StringToAccountType(value)) {
+    return false;
+  }
 
   // TODO(alemate): update code once user id is really a struct.
-  bool has_gaia_id = dict.GetString(kGAIAIdKey, &value);
-  if (has_gaia_id && account_id.GetGaiaId() == value)
-    return true;
+  switch (account_id.GetAccountType()) {
+    case AccountType::GOOGLE: {
+      bool has_gaia_id = dict.GetString(kGAIAIdKey, &value);
+      if (has_gaia_id && account_id.GetGaiaId() == value)
+        return true;
+      break;
+    }
+    case AccountType::ACTIVE_DIRECTORY: {
+      bool has_obj_guid = dict.GetString(kObjGuidKey, &value);
+      if (has_obj_guid && account_id.GetObjGuid() == value)
+        return true;
+      break;
+    }
+    case AccountType::UNKNOWN: {
+    }
+  }
 
   bool has_email = dict.GetString(kCanonicalEmail, &value);
   if (has_email && account_id.GetUserEmail() == value)
@@ -76,8 +100,20 @@
   if (!account_id.GetUserEmail().empty())
     dict.SetString(kCanonicalEmail, account_id.GetUserEmail());
 
-  if (!account_id.GetGaiaId().empty())
-    dict.SetString(kGAIAIdKey, account_id.GetGaiaId());
+  switch (account_id.GetAccountType()) {
+    case AccountType::GOOGLE:
+      if (!account_id.GetGaiaId().empty())
+        dict.SetString(kGAIAIdKey, account_id.GetGaiaId());
+      break;
+    case AccountType::ACTIVE_DIRECTORY:
+      if (!account_id.GetObjGuid().empty())
+        dict.SetString(kObjGuidKey, account_id.GetObjGuid());
+      break;
+    case AccountType::UNKNOWN:
+      return;
+  }
+  dict.SetString(kAccountTypeKey,
+                 AccountId::AccountTypeToString(account_id.GetAccountType()));
 }
 
 }  // namespace
@@ -216,48 +252,76 @@
 }
 
 AccountId GetAccountId(const std::string& user_email,
-                       const std::string& gaia_id) {
+                       const std::string& id,
+                       const AccountType& account_type) {
+  DCHECK((id.empty() && account_type == AccountType::UNKNOWN) ||
+         (!id.empty() && account_type != AccountType::UNKNOWN));
   // In tests empty accounts are possible.
-  if (user_email.empty() && gaia_id.empty())
+  if (user_email.empty() && id.empty() &&
+      account_type == AccountType::UNKNOWN) {
     return EmptyAccountId();
+  }
 
   AccountId result(EmptyAccountId());
   // UserManager is usually NULL in unit tests.
-  if (UserManager::IsInitialized() &&
-      UserManager::Get()->GetPlatformKnownUserId(user_email, gaia_id,
-                                                 &result)) {
+  if (account_type == AccountType::UNKNOWN && UserManager::IsInitialized() &&
+      UserManager::Get()->GetPlatformKnownUserId(user_email, id, &result)) {
     return result;
   }
 
-  // We can have several users with the same gaia_id but different e-mails.
-  // The opposite case is not possible.
   std::string stored_gaia_id;
+  std::string stored_obj_guid;
   const std::string sanitized_email =
       user_email.empty()
           ? std::string()
           : gaia::CanonicalizeEmail(gaia::SanitizeEmail(user_email));
 
-  if (!sanitized_email.empty() &&
-      GetStringPref(AccountId::FromUserEmail(sanitized_email), kGAIAIdKey,
-                    &stored_gaia_id)) {
-    if (!gaia_id.empty() && gaia_id != stored_gaia_id)
-      LOG(ERROR) << "User gaia id has changed. Sync will not work.";
+  if (!sanitized_email.empty()) {
+    if (GetStringPref(AccountId::FromUserEmail(sanitized_email), kGAIAIdKey,
+                      &stored_gaia_id)) {
+      if (!id.empty()) {
+        DCHECK(account_type == AccountType::GOOGLE);
+        if (id != stored_gaia_id)
+          LOG(ERROR) << "User gaia id has changed. Sync will not work.";
+      }
 
-    // gaia_id is associated with cryptohome.
-    return AccountId::FromUserEmailGaiaId(sanitized_email, stored_gaia_id);
+      // gaia_id is associated with cryptohome.
+      return AccountId::FromUserEmailGaiaId(sanitized_email, stored_gaia_id);
+    }
+
+    if (GetStringPref(AccountId::FromUserEmail(sanitized_email), kObjGuidKey,
+                      &stored_obj_guid)) {
+      if (!id.empty()) {
+        DCHECK(account_type == AccountType::ACTIVE_DIRECTORY);
+        if (id != stored_obj_guid)
+          LOG(ERROR) << "User object guid has changed. Sync will not work.";
+      }
+
+      // obj_guid is associated with cryptohome.
+      return AccountId::AdFromUserEmailObjGuid(sanitized_email,
+                                               stored_obj_guid);
+    }
   }
 
   std::string stored_email;
-  // GetStringPref() returns the first user record that matches
-  // given ID. So we will get the first one if there are multiples.
-  if (!gaia_id.empty() && GetStringPref(AccountId::FromGaiaId(gaia_id),
-                                        kCanonicalEmail, &stored_email)) {
-    return AccountId::FromUserEmailGaiaId(stored_email, gaia_id);
+  switch (account_type) {
+    case AccountType::GOOGLE:
+      if (GetStringPref(AccountId::FromGaiaId(id), kCanonicalEmail,
+                        &stored_email)) {
+        return AccountId::FromUserEmailGaiaId(stored_email, id);
+      }
+      return AccountId::FromUserEmailGaiaId(sanitized_email, id);
+    case AccountType::ACTIVE_DIRECTORY:
+      if (GetStringPref(AccountId::AdFromObjGuid(id), kCanonicalEmail,
+                        &stored_email)) {
+        return AccountId::AdFromUserEmailObjGuid(stored_email, id);
+      }
+      return AccountId::AdFromUserEmailObjGuid(sanitized_email, id);
+    case AccountType::UNKNOWN:
+      return AccountId::FromUserEmail(sanitized_email);
   }
-
-  return (gaia_id.empty()
-              ? AccountId::FromUserEmail(user_email)
-              : AccountId::FromUserEmailGaiaId(user_email, gaia_id));
+  NOTREACHED();
+  return EmptyAccountId();
 }
 
 std::vector<AccountId> GetKnownAccountIds() {
@@ -274,10 +338,30 @@
     if (known_users->GetDictionary(i, &element)) {
       std::string email;
       std::string gaia_id;
+      std::string obj_guid;
       const bool has_email = element->GetString(kCanonicalEmail, &email);
       const bool has_gaia_id = element->GetString(kGAIAIdKey, &gaia_id);
-      if (has_email || has_gaia_id)
-        result.push_back(AccountId::FromUserEmailGaiaId(email, gaia_id));
+      const bool has_obj_guid = element->GetString(kObjGuidKey, &obj_guid);
+      AccountType account_type = AccountType::GOOGLE;
+      std::string account_type_string;
+      if (element->GetString(kAccountTypeKey, &account_type_string)) {
+        account_type = AccountId::StringToAccountType(account_type_string);
+      }
+      switch (account_type) {
+        case AccountType::GOOGLE:
+          if (has_email || has_gaia_id) {
+            result.push_back(AccountId::FromUserEmailGaiaId(email, gaia_id));
+          }
+          break;
+        case AccountType::ACTIVE_DIRECTORY:
+          if (has_email && has_obj_guid) {
+            result.push_back(
+                AccountId::AdFromUserEmailObjGuid(email, obj_guid));
+          }
+          break;
+        default:
+          NOTREACHED() << "Unknown account type";
+      }
     }
   }
   return result;
@@ -304,6 +388,23 @@
 
 void UpdateGaiaID(const AccountId& account_id, const std::string& gaia_id) {
   SetStringPref(account_id, kGAIAIdKey, gaia_id);
+  SetStringPref(account_id, kAccountTypeKey,
+                AccountId::AccountTypeToString(AccountType::GOOGLE));
+}
+
+void UpdateId(const AccountId& account_id) {
+  switch (account_id.GetAccountType()) {
+    case AccountType::GOOGLE:
+      SetStringPref(account_id, kGAIAIdKey, account_id.GetGaiaId());
+      break;
+    case AccountType::ACTIVE_DIRECTORY:
+      SetStringPref(account_id, kObjGuidKey, account_id.GetObjGuid());
+      break;
+    case AccountType::UNKNOWN:
+      return;
+  }
+  SetStringPref(account_id, kAccountTypeKey,
+                AccountId::AccountTypeToString(account_id.GetAccountType()));
 }
 
 bool FindGaiaID(const AccountId& account_id, std::string* out_value) {
diff --git a/components/user_manager/known_user.h b/components/user_manager/known_user.h
index c0dbd9cd..245ed57 100644
--- a/components/user_manager/known_user.h
+++ b/components/user_manager/known_user.h
@@ -11,6 +11,7 @@
 #include "components/user_manager/user_manager_export.h"
 
 class AccountId;
+enum class AccountType;
 class PrefRegistrySimple;
 
 namespace base {
@@ -74,7 +75,8 @@
 // gaia_id.
 // This is a temporary call while migrating to AccountId.
 AccountId USER_MANAGER_EXPORT GetAccountId(const std::string& user_email,
-                                           const std::string& gaia_id);
+                                           const std::string& id,
+                                           const AccountType& account_type);
 
 // Returns true if |subsystem| data was migrated to GaiaId for the |account_id|.
 bool USER_MANAGER_EXPORT GetGaiaIdMigrationStatus(const AccountId& account_id,
@@ -91,6 +93,10 @@
 void USER_MANAGER_EXPORT UpdateGaiaID(const AccountId& account_id,
                                       const std::string& gaia_id);
 
+// Updates |account_id.account_type_| and |account_id.GetGaiaId()| or
+// |account_id.GetObjGuid()| for user with |account_id|.
+void USER_MANAGER_EXPORT UpdateId(const AccountId& account_id);
+
 // Find GAIA ID for user with |account_id|, fill in |out_value| and return
 // true
 // if GAIA ID was found or false otherwise.
diff --git a/components/user_manager/user_manager_base.cc b/components/user_manager/user_manager_base.cc
index 6710f25d..24772d6b 100644
--- a/components/user_manager/user_manager_base.cc
+++ b/components/user_manager/user_manager_base.cc
@@ -494,7 +494,8 @@
       continue;
     }
 
-    const AccountId account_id = known_user::GetAccountId(email, std::string());
+    const AccountId account_id = known_user::GetAccountId(
+        email, std::string() /* id */, AccountType::UNKNOWN);
 
     if (existing_users.find(account_id) != existing_users.end() ||
         !users_set->insert(account_id).second) {
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index aed3789..32e5563 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -733,14 +733,13 @@
   // Only use discardable_memory::DiscardableSharedMemoryManager when Chrome is
   // not running in mus+ash.
   if (!service_manager::ServiceManagerIsRemote()) {
-    auto* discardable_shared_memory_manager =
-        discardable_memory::DiscardableSharedMemoryManager::CreateInstance();
-
+    discardable_shared_memory_manager_ =
+        base::MakeUnique<discardable_memory::DiscardableSharedMemoryManager>();
     // TODO(boliu): kSingleProcess check is a temporary workaround for
     // in-process Android WebView. crbug.com/503724 tracks proper fix.
     if (!parsed_command_line_.HasSwitch(switches::kSingleProcess)) {
       base::DiscardableMemoryAllocator::SetInstance(
-          discardable_shared_memory_manager);
+          discardable_shared_memory_manager_.get());
     }
   }
 
@@ -1011,10 +1010,10 @@
 
     // Otherwise this thread ID will be backed by a SingleThreadTaskRunner using
     // |non_ui_non_io_task_runner_traits| (which can be augmented below).
-    // TODO(gab): Existing non-UI/non-IO BrowserThreads allow waiting so the
-    // initial redirection will as well but they probably don't need to.
+    // TODO(gab): Existing non-UI/non-IO BrowserThreads allow sync primitives so
+    // the initial redirection will as well but they probably don't need to.
     base::TaskTraits non_ui_non_io_task_runner_traits =
-        base::TaskTraits().WithFileIO().WithWait();
+        base::TaskTraits().MayBlock().WithSyncPrimitives();
 
     switch (thread_id) {
       case BrowserThread::DB:
diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h
index 8f82a58..ad6c04c 100644
--- a/content/browser/browser_main_loop.h
+++ b/content/browser/browser_main_loop.h
@@ -39,6 +39,10 @@
 class TimeZoneMonitor;
 }
 
+namespace discardable_memory {
+class DiscardableSharedMemoryManager;
+}
+
 namespace media {
 #if defined(OS_WIN)
 class SystemMessageWindowWin;
@@ -145,6 +149,10 @@
   device::TimeZoneMonitor* time_zone_monitor() const {
     return time_zone_monitor_.get();
   }
+  discardable_memory::DiscardableSharedMemoryManager*
+  discardable_shared_memory_manager() const {
+    return discardable_shared_memory_manager_.get();
+  }
   midi::MidiService* midi_service() const { return midi_service_.get(); }
   base::Thread* indexed_db_thread() const { return indexed_db_thread_.get(); }
 
@@ -312,6 +320,8 @@
   std::unique_ptr<MediaStreamManager> media_stream_manager_;
   std::unique_ptr<SpeechRecognitionManagerImpl> speech_recognition_manager_;
   std::unique_ptr<device::TimeZoneMonitor> time_zone_monitor_;
+  std::unique_ptr<discardable_memory::DiscardableSharedMemoryManager>
+      discardable_shared_memory_manager_;
   scoped_refptr<SaveFileManager> save_file_manager_;
 
   // DO NOT add members here. Add them to the right categories above.
diff --git a/content/browser/cache_storage/cache_storage.cc b/content/browser/cache_storage/cache_storage.cc
index ce791fe..63b03c5 100644
--- a/content/browser/cache_storage/cache_storage.cc
+++ b/content/browser/cache_storage/cache_storage.cc
@@ -817,22 +817,21 @@
 // Call this once the last handle to a doomed cache is gone. It's okay if this
 // doesn't get to complete before shutdown, the cache will be removed from disk
 // on next startup in that case.
-void CacheStorage::DeleteCacheFinalize(
-    std::unique_ptr<CacheStorageCache> doomed_cache) {
-  CacheStorageCache* cache = doomed_cache.get();
-  cache->Size(base::Bind(&CacheStorage::DeleteCacheDidGetSize,
-                         weak_factory_.GetWeakPtr(),
-                         base::Passed(std::move(doomed_cache))));
+void CacheStorage::DeleteCacheFinalize(CacheStorageCache* doomed_cache) {
+  doomed_cache->Size(base::Bind(&CacheStorage::DeleteCacheDidGetSize,
+                                weak_factory_.GetWeakPtr(), doomed_cache));
 }
 
-void CacheStorage::DeleteCacheDidGetSize(
-    std::unique_ptr<CacheStorageCache> cache,
-    int64_t cache_size) {
+void CacheStorage::DeleteCacheDidGetSize(CacheStorageCache* doomed_cache,
+                                         int64_t cache_size) {
   quota_manager_proxy_->NotifyStorageModified(
       storage::QuotaClient::kServiceWorkerCache, origin_,
       storage::kStorageTypeTemporary, -1 * cache_size);
 
-  cache_loader_->CleanUpDeletedCache(cache.get());
+  cache_loader_->CleanUpDeletedCache(doomed_cache);
+  auto doomed_caches_iter = doomed_caches_.find(doomed_cache);
+  DCHECK(doomed_caches_iter != doomed_caches_.end());
+  doomed_caches_.erase(doomed_caches_iter);
 }
 
 void CacheStorage::EnumerateCachesImpl(const StringsCallback& callback) {
@@ -953,8 +952,7 @@
     auto doomed_caches_iter = doomed_caches_.find(cache);
     if (doomed_caches_iter != doomed_caches_.end()) {
       // The last reference to a doomed cache is gone, perform clean up.
-      DeleteCacheFinalize(std::move(doomed_caches_iter->second));
-      doomed_caches_.erase(doomed_caches_iter);
+      DeleteCacheFinalize(cache);
       return;
     }
 
diff --git a/content/browser/cache_storage/cache_storage.h b/content/browser/cache_storage/cache_storage.h
index e0569f1..e40c937 100644
--- a/content/browser/cache_storage/cache_storage.h
+++ b/content/browser/cache_storage/cache_storage.h
@@ -166,8 +166,8 @@
       const StringVector& original_ordered_cache_names,
       const BoolAndErrorCallback& callback,
       bool success);
-  void DeleteCacheFinalize(std::unique_ptr<CacheStorageCache> doomed_cache);
-  void DeleteCacheDidGetSize(std::unique_ptr<CacheStorageCache> cache,
+  void DeleteCacheFinalize(CacheStorageCache* doomed_cache);
+  void DeleteCacheDidGetSize(CacheStorageCache* doomed_cache,
                              int64_t cache_size);
   void DeleteCacheDidCleanUp(bool success);
 
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index efa835b..2c04eda 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -209,6 +209,11 @@
   if (frame_entry.method() == "POST")
     request_body = frame_entry.GetPostData();
 
+  base::Optional<url::Origin> initiator =
+      frame_tree_node->IsMainFrame()
+          ? base::Optional<url::Origin>()
+          : base::Optional<url::Origin>(
+                frame_tree_node->frame_tree()->root()->current_origin());
   std::unique_ptr<NavigationRequest> navigation_request(new NavigationRequest(
       frame_tree_node, entry.ConstructCommonNavigationParams(
                            frame_entry, request_body, dest_url, dest_referrer,
@@ -216,7 +221,7 @@
       BeginNavigationParams(entry.extra_headers(), net::LOAD_NORMAL,
                             false,  // has_user_gestures
                             false,  // skip_service_worker
-                            REQUEST_CONTEXT_TYPE_LOCATION),
+                            REQUEST_CONTEXT_TYPE_LOCATION, initiator),
       entry.ConstructRequestNavigationParams(
           frame_entry, is_same_document_history_load,
           is_history_navigation_in_new_child,
@@ -612,8 +617,8 @@
       frame_tree_node_->navigator()->GetController()->GetBrowserContext(),
       base::MakeUnique<NavigationRequestInfo>(
           common_params_, begin_params_, first_party_for_cookies,
-          frame_tree_node_->current_origin(), frame_tree_node_->IsMainFrame(),
-          parent_is_main_frame, IsSecureFrame(frame_tree_node_->parent()),
+          frame_tree_node_->IsMainFrame(), parent_is_main_frame,
+          IsSecureFrame(frame_tree_node_->parent()),
           frame_tree_node_->frame_tree_node_id(), is_for_guests_only,
           report_raw_headers, navigating_frame_host->GetVisibilityState()),
       std::move(navigation_ui_data),
diff --git a/content/browser/frame_host/navigation_request_info.cc b/content/browser/frame_host/navigation_request_info.cc
index c93938b..65d0007a 100644
--- a/content/browser/frame_host/navigation_request_info.cc
+++ b/content/browser/frame_host/navigation_request_info.cc
@@ -11,7 +11,6 @@
     const CommonNavigationParams& common_params,
     const BeginNavigationParams& begin_params,
     const GURL& first_party_for_cookies,
-    const url::Origin& request_initiator,
     bool is_main_frame,
     bool parent_is_main_frame,
     bool are_ancestors_secure,
@@ -22,7 +21,6 @@
     : common_params(common_params),
       begin_params(begin_params),
       first_party_for_cookies(first_party_for_cookies),
-      request_initiator(request_initiator),
       is_main_frame(is_main_frame),
       parent_is_main_frame(parent_is_main_frame),
       are_ancestors_secure(are_ancestors_secure),
diff --git a/content/browser/frame_host/navigation_request_info.h b/content/browser/frame_host/navigation_request_info.h
index 35f88c8..ce81bb8 100644
--- a/content/browser/frame_host/navigation_request_info.h
+++ b/content/browser/frame_host/navigation_request_info.h
@@ -25,7 +25,6 @@
   NavigationRequestInfo(const CommonNavigationParams& common_params,
                         const BeginNavigationParams& begin_params,
                         const GURL& first_party_for_cookies,
-                        const url::Origin& request_initiator,
                         bool is_main_frame,
                         bool parent_is_main_frame,
                         bool are_ancestors_secure,
@@ -42,9 +41,6 @@
   // checked by the third-party cookie blocking policy.
   const GURL first_party_for_cookies;
 
-  // The origin of the context which initiated the request.
-  const url::Origin request_initiator;
-
   const bool is_main_frame;
   const bool parent_is_main_frame;
 
diff --git a/content/browser/loader/navigation_url_loader_unittest.cc b/content/browser/loader/navigation_url_loader_unittest.cc
index 0db2323..2b60f650 100644
--- a/content/browser/loader/navigation_url_loader_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_unittest.cc
@@ -107,14 +107,14 @@
       const GURL& url,
       NavigationURLLoaderDelegate* delegate) {
     BeginNavigationParams begin_params(std::string(), net::LOAD_NORMAL, false,
-                                       false, REQUEST_CONTEXT_TYPE_LOCATION);
+                                       false, REQUEST_CONTEXT_TYPE_LOCATION,
+                                       url::Origin(url));
     CommonNavigationParams common_params;
     common_params.url = url;
     std::unique_ptr<NavigationRequestInfo> request_info(
-        new NavigationRequestInfo(
-            common_params, begin_params, url, url::Origin(url), true, false,
-            false, -1, false, false, blink::WebPageVisibilityStateVisible));
-
+        new NavigationRequestInfo(common_params, begin_params, url, true, false,
+                                  false, -1, false, false,
+                                  blink::WebPageVisibilityStateVisible));
     return NavigationURLLoader::Create(browser_context_.get(),
                                        std::move(request_info), nullptr,
                                        nullptr, nullptr, delegate);
diff --git a/content/browser/loader/resource_dispatcher_host_browsertest.cc b/content/browser/loader/resource_dispatcher_host_browsertest.cc
index b3e1c75..7b2dbb6 100644
--- a/content/browser/loader/resource_dispatcher_host_browsertest.cc
+++ b/content/browser/loader/resource_dispatcher_host_browsertest.cc
@@ -24,6 +24,7 @@
 #include "content/public/browser/resource_dispatcher_host_delegate.h"
 #include "content/public/browser/resource_request_info.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/browser_side_navigation_policy.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -879,10 +880,23 @@
   // All resources loaded directly by the top-level document (including the
   // top-level document itself) should have a |first_party| and |initiator|
   // that match the URL of the top-level document.
-  for (auto* request : delegate_->data()) {
-    SCOPED_TRACE(request->url);
-    EXPECT_EQ(top_url, request->first_party);
-    EXPECT_EQ(top_origin, request->initiator);
+  // PlzNavigate: the document itself should have an empty initiator.
+  if (IsBrowserSideNavigationEnabled()) {
+    const RequestDataForDelegate* first_request = delegate_->data()[0];
+    EXPECT_EQ(top_url, first_request->first_party);
+    EXPECT_FALSE(first_request->initiator.has_value());
+    for (size_t i = 1; i < delegate_->data().size(); i++) {
+      const RequestDataForDelegate* request = delegate_->data()[i];
+      EXPECT_EQ(top_url, request->first_party);
+      ASSERT_TRUE(request->initiator.has_value());
+      EXPECT_EQ(top_origin, request->initiator);
+    }
+  } else {
+    for (auto* request : delegate_->data()) {
+      SCOPED_TRACE(request->url);
+      EXPECT_EQ(top_url, request->first_party);
+      EXPECT_EQ(top_origin, request->initiator);
+    }
   }
 }
 
@@ -902,10 +916,16 @@
 
   // The first items loaded are the top-level and nested documents. These should
   // both have a |first_party| and |initiator| that match the URL of the
-  // top-level document:
+  // top-level document.
+  // PlzNavigate: the top-level initiator is null.
   EXPECT_EQ(top_url, delegate_->data()[0]->url);
   EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
-  EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  if (IsBrowserSideNavigationEnabled()) {
+    EXPECT_FALSE(delegate_->data()[0]->initiator.has_value());
+  } else {
+    ASSERT_TRUE(delegate_->data()[0]->initiator.has_value());
+    EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  }
 
   EXPECT_EQ(nested_url, delegate_->data()[1]->url);
   EXPECT_EQ(top_url, delegate_->data()[1]->first_party);
@@ -934,9 +954,15 @@
 
   // User-initiated top-level navigations have a first-party and initiator that
   // matches the URL to which they navigate.
+  // PlzNavigate: the top-level initiator is null.
   EXPECT_EQ(top_url, delegate_->data()[0]->url);
   EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
-  EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  if (IsBrowserSideNavigationEnabled()) {
+    EXPECT_FALSE(delegate_->data()[0]->initiator.has_value());
+  } else {
+    ASSERT_TRUE(delegate_->data()[0]->initiator.has_value());
+    EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  }
 
   // Subresource requests have a first-party and initiator that matches the
   // document in which they're embedded.
@@ -973,9 +999,15 @@
 
   // User-initiated top-level navigations have a first-party and initiator that
   // matches the URL to which they navigate, even if they fail to load.
+  // PlzNavigate: the top-level initiator is null.
   EXPECT_EQ(top_url, delegate_->data()[0]->url);
   EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
-  EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  if (IsBrowserSideNavigationEnabled()) {
+    EXPECT_FALSE(delegate_->data()[0]->initiator.has_value());
+  } else {
+    ASSERT_TRUE(delegate_->data()[0]->initiator.has_value());
+    EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  }
 
   // Auxiliary navigations have a first-party that matches the URL to which they
   // navigate, and an initiator that matches the document that triggered them.
@@ -1014,9 +1046,15 @@
 
   // User-initiated top-level navigations have a first-party and initiator that
   // matches the URL to which they navigate, even if they fail to load.
+  // PlzNavigate: the top-level initiator is null.
   EXPECT_EQ(top_url, delegate_->data()[0]->url);
   EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
-  EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  if (IsBrowserSideNavigationEnabled()) {
+    EXPECT_FALSE(delegate_->data()[0]->initiator.has_value());
+  } else {
+    ASSERT_TRUE(delegate_->data()[0]->initiator.has_value());
+    EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  }
 
   // Auxiliary navigations have a first-party that matches the URL to which they
   // navigate, and an initiator that matches the document that triggered them.
@@ -1038,9 +1076,15 @@
 
   // User-initiated top-level navigations have a first-party and initiator that
   // matches the URL to which they navigate, even if they fail to load.
+  // PlzNavigate: the top-level initiator is null.
   EXPECT_EQ(top_url, delegate_->data()[0]->url);
   EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
-  EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  if (IsBrowserSideNavigationEnabled()) {
+    EXPECT_FALSE(delegate_->data()[0]->initiator.has_value());
+  } else {
+    ASSERT_TRUE(delegate_->data()[0]->initiator.has_value());
+    EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
@@ -1063,9 +1107,15 @@
 
   // User-initiated top-level navigations have a first-party and initiator that
   // matches the URL to which they navigate.
+  // PlzNavigate: the top-level initiator is null.
   EXPECT_EQ(top_url, delegate_->data()[0]->url);
   EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
-  EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  if (IsBrowserSideNavigationEnabled()) {
+    EXPECT_FALSE(delegate_->data()[0]->initiator.has_value());
+  } else {
+    ASSERT_TRUE(delegate_->data()[0]->initiator.has_value());
+    EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
+  }
 
   EXPECT_EQ(top_js_url, delegate_->data()[1]->url);
   EXPECT_EQ(top_url, delegate_->data()[1]->first_party);
diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc
index a699e0ed..d3d18d5c 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.cc
+++ b/content/browser/loader/resource_dispatcher_host_impl.cc
@@ -2179,7 +2179,7 @@
   new_request->set_method(info.common_params.method);
   new_request->set_first_party_for_cookies(
       info.first_party_for_cookies);
-  new_request->set_initiator(info.request_initiator);
+  new_request->set_initiator(info.begin_params.initiator_origin);
   if (info.is_main_frame) {
     new_request->set_first_party_url_policy(
         net::URLRequest::UPDATE_FIRST_PARTY_URL_ON_REDIRECT);
diff --git a/content/browser/loader/resource_dispatcher_host_unittest.cc b/content/browser/loader/resource_dispatcher_host_unittest.cc
index 6c1a116..dcb94f7 100644
--- a/content/browser/loader/resource_dispatcher_host_unittest.cc
+++ b/content/browser/loader/resource_dispatcher_host_unittest.cc
@@ -1083,13 +1083,14 @@
       // Make a navigation request.
       TestNavigationURLLoaderDelegate delegate;
       BeginNavigationParams begin_params(std::string(), net::LOAD_NORMAL, false,
-                                         false, REQUEST_CONTEXT_TYPE_LOCATION);
+                                         false, REQUEST_CONTEXT_TYPE_LOCATION,
+                                         url::Origin(url));
       CommonNavigationParams common_params;
       common_params.url = url;
       std::unique_ptr<NavigationRequestInfo> request_info(
-          new NavigationRequestInfo(
-              common_params, begin_params, url, url::Origin(url), true, false,
-              false, -1, false, false, blink::WebPageVisibilityStateVisible));
+          new NavigationRequestInfo(common_params, begin_params, url, true,
+                                    false, false, -1, false, false,
+                                    blink::WebPageVisibilityStateVisible));
       std::unique_ptr<NavigationURLLoader> test_loader =
           NavigationURLLoader::Create(browser_context_.get(),
                                       std::move(request_info), nullptr, nullptr,
@@ -2640,13 +2641,13 @@
     // Create a NavigationRequest.
     TestNavigationURLLoaderDelegate delegate;
     BeginNavigationParams begin_params(std::string(), net::LOAD_NORMAL, false,
-                                       false, REQUEST_CONTEXT_TYPE_LOCATION);
+                                       false, REQUEST_CONTEXT_TYPE_LOCATION,
+                                       url::Origin(download_url));
     CommonNavigationParams common_params;
     common_params.url = download_url;
     std::unique_ptr<NavigationRequestInfo> request_info(
         new NavigationRequestInfo(common_params, begin_params, download_url,
-                                  url::Origin(download_url), true, false, false,
-                                  -1, false, false,
+                                  true, false, false, -1, false, false,
                                   blink::WebPageVisibilityStateVisible));
     std::unique_ptr<NavigationURLLoader> loader = NavigationURLLoader::Create(
         browser_context_.get(), std::move(request_info), nullptr, nullptr,
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index f75c596..c4c30d20 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -1304,10 +1304,11 @@
   // Chrome browser process only provides DiscardableSharedMemory service when
   // Chrome is not running in mus+ash.
   if (!service_manager::ServiceManagerIsRemote()) {
-    registry->AddInterface(base::Bind(
-        &discardable_memory::DiscardableSharedMemoryManager::Bind,
-        base::Unretained(discardable_memory::DiscardableSharedMemoryManager::
-                             GetInstance())));
+    discardable_memory::DiscardableSharedMemoryManager* manager =
+        BrowserMainLoop::GetInstance()->discardable_shared_memory_manager();
+    registry->AddInterface(
+        base::Bind(&discardable_memory::DiscardableSharedMemoryManager::Bind,
+                   base::Unretained(manager)));
   }
 
   GetContentClient()->browser()->ExposeInterfacesToRenderer(registry.get(),
diff --git a/content/browser/service_worker/service_worker_context_request_handler_unittest.cc b/content/browser/service_worker/service_worker_context_request_handler_unittest.cc
index 912210f..5c6a1d7 100644
--- a/content/browser/service_worker/service_worker_context_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_request_handler_unittest.cc
@@ -22,6 +22,7 @@
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "net/base/load_flags.h"
 #include "net/url_request/url_request_context.h"
+#include "storage/browser/blob/blob_storage_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace content {
@@ -53,7 +54,7 @@
         new ServiceWorkerProviderHost(
             helper_->mock_render_process_id(),
             MSG_ROUTING_NONE /* render_frame_id */, 1 /* provider_id */,
-            SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+            SERVICE_WORKER_PROVIDER_FOR_CONTROLLER,
             ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
             context()->AsWeakPtr(), nullptr));
     provider_host_ = host->AsWeakPtr();
@@ -81,6 +82,7 @@
   MockURLRequestDelegate url_request_delegate_;
   GURL scope_;
   GURL script_url_;
+  storage::BlobStorageContext blob_storage_context_;
 };
 
 class ServiceWorkerContextRequestHandlerTestP
@@ -192,6 +194,29 @@
       sw_job->net_request_.get()));
 }
 
+// Tests starting a service worker when the skip_service_worker flag is on. The
+// flag should be ignored.
+TEST_P(ServiceWorkerContextRequestHandlerTestP,
+       SkipServiceWorkerForServiceWorkerRequest) {
+  // Conduct a resource fetch for the main script.
+  version_->SetStatus(ServiceWorkerVersion::NEW);
+  provider_host_->running_hosted_version_ = version_;
+  const GURL kScriptUrl("http://host/script.js");
+  std::unique_ptr<net::URLRequest> request = url_request_context_.CreateRequest(
+      kScriptUrl, net::DEFAULT_PRIORITY, &url_request_delegate_);
+  ServiceWorkerRequestHandler::InitializeHandler(
+      request.get(), helper_->context_wrapper(), &blob_storage_context_,
+      helper_->mock_render_process_id(), provider_host_->provider_id(),
+      true /* skip_service_worker */, FETCH_REQUEST_MODE_NO_CORS,
+      FETCH_CREDENTIALS_MODE_OMIT, FetchRedirectMode::FOLLOW_MODE,
+      RESOURCE_TYPE_SERVICE_WORKER, REQUEST_CONTEXT_TYPE_SERVICE_WORKER,
+      REQUEST_CONTEXT_FRAME_TYPE_NONE, nullptr);
+  // Verify a ServiceWorkerRequestHandler was created.
+  ServiceWorkerRequestHandler* handler =
+      ServiceWorkerRequestHandler::GetHandler(request.get());
+  EXPECT_TRUE(handler);
+}
+
 INSTANTIATE_TEST_CASE_P(ServiceWorkerContextRequestHandlerTest,
                         ServiceWorkerContextRequestHandlerTestP,
                         testing::Bool());
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.cc b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
index 1dd7ca6..6e5bb22 100644
--- a/content/browser/service_worker/service_worker_fetch_dispatcher.cc
+++ b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
@@ -428,7 +428,10 @@
   ResourceRequest request;
   request.method = original_request->method();
   request.url = original_request->url();
-  request.request_initiator = original_request->initiator();
+  // TODO(horo): Set first_party_for_cookies to support Same-site Cookies.
+  request.request_initiator = original_request->initiator().has_value()
+                                  ? original_request->initiator()
+                                  : url::Origin(original_request->url());
   request.referrer = GURL(original_request->referrer());
   request.referrer_policy = original_info->GetReferrerPolicy();
   request.visibility_state = original_info->GetVisibilityState();
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index b816b742..bf69489 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -375,6 +375,20 @@
     base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
     scoped_refptr<ResourceRequestBodyImpl> body,
     bool skip_service_worker) {
+  // |skip_service_worker| is meant to apply to requests that could be handled
+  // by a service worker, as opposed to requests for the service worker script
+  // itself. So ignore it here for the service worker script and its imported
+  // scripts.
+  // TODO(falken): Really it should be treated as an error to set
+  // |skip_service_worker| for requests to start the service worker, but it's
+  // difficult to fix that renderer-side, since we don't know whether a request
+  // is for a service worker without access to IsHostToRunningServiceWorker() as
+  // that state is stored browser-side.
+  if (IsHostToRunningServiceWorker() &&
+      (resource_type == RESOURCE_TYPE_SERVICE_WORKER ||
+       resource_type == RESOURCE_TYPE_SCRIPT)) {
+    skip_service_worker = false;
+  }
   if (skip_service_worker) {
     if (!ServiceWorkerUtils::IsMainResourceType(resource_type))
       return std::unique_ptr<ServiceWorkerRequestHandler>();
diff --git a/content/browser/service_worker/service_worker_provider_host.h b/content/browser/service_worker/service_worker_provider_host.h
index e12d30cd..d4c4041 100644
--- a/content/browser/service_worker/service_worker_provider_host.h
+++ b/content/browser/service_worker/service_worker_provider_host.h
@@ -289,6 +289,8 @@
                            UpdateForceBypassCache);
   FRIEND_TEST_ALL_PREFIXES(ServiceWorkerContextRequestHandlerTestP,
                            ServiceWorkerDataRequestAnnotation);
+  FRIEND_TEST_ALL_PREFIXES(ServiceWorkerContextRequestHandlerTestP,
+                           SkipServiceWorkerForServiceWorkerRequest);
   FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProviderHostTestP, ContextSecurity);
 
   struct OneShotGetReadyCallback {
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index aad2aac1..d82e6c1 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -349,6 +349,7 @@
   IPC_STRUCT_TRAITS_MEMBER(request_context_type)
   IPC_STRUCT_TRAITS_MEMBER(searchable_form_url)
   IPC_STRUCT_TRAITS_MEMBER(searchable_form_encoding)
+  IPC_STRUCT_TRAITS_MEMBER(initiator_origin)
 IPC_STRUCT_TRAITS_END()
 
 IPC_STRUCT_TRAITS_BEGIN(content::StartNavigationParams)
diff --git a/content/common/navigation_params.cc b/content/common/navigation_params.cc
index 95a9206..51220c0 100644
--- a/content/common/navigation_params.cc
+++ b/content/common/navigation_params.cc
@@ -91,16 +91,20 @@
     int load_flags,
     bool has_user_gesture,
     bool skip_service_worker,
-    RequestContextType request_context_type)
+    RequestContextType request_context_type,
+    const base::Optional<url::Origin>& initiator_origin)
     : headers(headers),
       load_flags(load_flags),
       has_user_gesture(has_user_gesture),
       skip_service_worker(skip_service_worker),
-      request_context_type(request_context_type) {}
+      request_context_type(request_context_type),
+      initiator_origin(initiator_origin) {}
 
 BeginNavigationParams::BeginNavigationParams(
     const BeginNavigationParams& other) = default;
 
+BeginNavigationParams::~BeginNavigationParams() {}
+
 StartNavigationParams::StartNavigationParams()
     : transferred_request_child_id(-1),
       transferred_request_request_id(-1) {
diff --git a/content/common/navigation_params.h b/content/common/navigation_params.h
index 7fce0cc..86a5e7e 100644
--- a/content/common/navigation_params.h
+++ b/content/common/navigation_params.h
@@ -11,6 +11,7 @@
 #include <string>
 
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "content/common/content_export.h"
@@ -22,6 +23,7 @@
 #include "content/public/common/resource_response.h"
 #include "ui/base/page_transition_types.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 namespace content {
 
@@ -146,8 +148,10 @@
                         int load_flags,
                         bool has_user_gesture,
                         bool skip_service_worker,
-                        RequestContextType request_context_type);
+                        RequestContextType request_context_type,
+                        const base::Optional<url::Origin>& initiator_origin);
   BeginNavigationParams(const BeginNavigationParams& other);
+  ~BeginNavigationParams();
 
   // Additional HTTP request headers.
   std::string headers;
@@ -167,6 +171,11 @@
   // See WebSearchableFormData for a description of these.
   GURL searchable_form_url;
   std::string searchable_form_encoding;
+
+  // Indicates the initiator of the request. In auxilliary navigations, this is
+  // the origin of the document that triggered the navigation. This parameter
+  // can be null during browser-initiated navigations.
+  base::Optional<url::Origin> initiator_origin;
 };
 
 // Provided by the browser -----------------------------------------------------
diff --git a/content/renderer/DEPS b/content/renderer/DEPS
index af6b520..c5de582 100644
--- a/content/renderer/DEPS
+++ b/content/renderer/DEPS
@@ -38,5 +38,6 @@
   ],
   "render_thread_impl_discardable_memory_browsertest\.cc": [
     "+components/discardable_memory/service",
+    "+content/browser/browser_main_loop.h",
   ],
 }
diff --git a/content/renderer/media/renderer_webaudiodevice_impl.cc b/content/renderer/media/renderer_webaudiodevice_impl.cc
index 867da2e..c9d49bdd 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl.cc
+++ b/content/renderer/media/renderer_webaudiodevice_impl.cc
@@ -117,13 +117,9 @@
   }
   DCHECK_GE(delay, base::TimeDelta());
 
-  // TODO(xians): Remove the following |web_audio_source_data| after
-  // changing the blink interface.
-  WebVector<float*> web_audio_source_data(static_cast<size_t>(0));
-  client_callback_->render(web_audio_source_data, web_audio_dest_data,
-                           dest->frames(), delay.InSecondsF(),
-                           (delay_timestamp - base::TimeTicks()).InSecondsF(),
-                           prior_frames_skipped);
+  client_callback_->render(
+      web_audio_dest_data, dest->frames(), delay.InSecondsF(),
+      (delay_timestamp - base::TimeTicks()).InSecondsF(), prior_frames_skipped);
 
   return dest->frames();
 }
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 807848cc..664807b 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -6184,13 +6184,17 @@
          GetRequestContextFrameTypeForWebURLRequest(info.urlRequest) ==
              REQUEST_CONTEXT_FRAME_TYPE_NESTED);
 
+  base::Optional<url::Origin> initiator_origin =
+      info.urlRequest.requestorOrigin().isNull()
+          ? base::Optional<url::Origin>()
+          : base::Optional<url::Origin>(info.urlRequest.requestorOrigin());
   BeginNavigationParams begin_navigation_params(
       GetWebURLRequestHeaders(info.urlRequest),
       GetLoadFlagsForWebURLRequest(info.urlRequest),
       info.urlRequest.hasUserGesture(),
       info.urlRequest.skipServiceWorker() !=
           blink::WebURLRequest::SkipServiceWorker::None,
-      GetRequestContextTypeForWebURLRequest(info.urlRequest));
+      GetRequestContextTypeForWebURLRequest(info.urlRequest), initiator_origin);
 
   if (!info.form.isNull()) {
     WebSearchableFormData web_searchable_form_data(info.form);
diff --git a/content/renderer/render_thread_impl_discardable_memory_browsertest.cc b/content/renderer/render_thread_impl_discardable_memory_browsertest.cc
index a9cf64b..0fa7dcda 100644
--- a/content/renderer/render_thread_impl_discardable_memory_browsertest.cc
+++ b/content/renderer/render_thread_impl_discardable_memory_browsertest.cc
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #include "components/discardable_memory/client/client_discardable_shared_memory_manager.h"
 #include "components/discardable_memory/service/discardable_shared_memory_manager.h"
+#include "content/browser/browser_main_loop.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
@@ -74,7 +75,8 @@
   memory->Unlock();
 
   // Purge all unlocked memory.
-  discardable_memory::DiscardableSharedMemoryManager::GetInstance()
+  BrowserMainLoop::GetInstance()
+      ->discardable_shared_memory_manager()
       ->SetMemoryLimit(0);
 
   // Should fail as memory should have been purged.
@@ -114,7 +116,8 @@
   EXPECT_TRUE(memory);
   memory.reset();
 
-  EXPECT_GE(discardable_memory::DiscardableSharedMemoryManager::GetInstance()
+  EXPECT_GE(BrowserMainLoop::GetInstance()
+                ->discardable_shared_memory_manager()
                 ->GetBytesAllocated(),
             kSize);
 
@@ -124,7 +127,8 @@
   base::TimeTicks end =
       base::TimeTicks::Now() + base::TimeDelta::FromSeconds(5);
   while (base::TimeTicks::Now() < end) {
-    if (!discardable_memory::DiscardableSharedMemoryManager::GetInstance()
+    if (!BrowserMainLoop::GetInstance()
+             ->discardable_shared_memory_manager()
              ->GetBytesAllocated())
       break;
   }
diff --git a/content/test/data/gpu/pixel_offscreenCanvas_2d_resize_on_worker.html b/content/test/data/gpu/pixel_offscreenCanvas_2d_resize_on_worker.html
new file mode 100644
index 0000000..91efd142
--- /dev/null
+++ b/content/test/data/gpu/pixel_offscreenCanvas_2d_resize_on_worker.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+
+<!-- READ BEFORE UPDATING:
+If this test is updated make sure to increment the "revision" value of the
+associated test in content/test/gpu/gpu_tests/pixel_test_pages.py. This will ensure
+that the baseline images are regenerated on the next run.
+-->
+
+<html>
+<head>
+<title>OffscreenCanvas2d.commit with resizing: red rectangle in the middle.</title>
+<style type="text/css">
+.nomargin {
+  margin: 0px auto;
+}
+</style>
+<script id="myWorker" type="text/worker">
+self.onmessage = function(e) {
+  var transfered = e.data;
+
+  // Resize offscreenCanvas from 200X200 to 40X50.
+  transfered.width = 40;
+  transfered.height = 50;
+  var ctx2d = transfered.getContext('2d');
+
+  // The resultant image should be 100X80 red-color rectangle in the middle
+  // of green background.
+  ctx2d.fillStyle = "green";
+  ctx2d.fillRect(0, 0, transfered.width, transfered.height);
+  ctx2d.fillStyle = "red";
+  ctx2d.fillRect(10, 10, 20, 20);
+  ctx2d.commit();
+
+  self.postMessage("");
+};
+</script>
+<script>
+var g_swapsBeforeAck = 15;
+
+function makeWorker(script)
+{
+   var blob = new Blob([script]);
+   return new Worker(URL.createObjectURL(blob));
+}
+
+function waitForFinish()
+{
+  if (g_swapsBeforeAck == 0) {
+    domAutomationController.setAutomationId(1);
+    domAutomationController.send("SUCCESS");
+  } else {
+    g_swapsBeforeAck--;
+    document.getElementById('container').style.zIndex = g_swapsBeforeAck + 1;
+    window.webkitRequestAnimationFrame(waitForFinish);
+  }
+}
+
+function main()
+{
+  var canvas2D = document.getElementById("c");
+  var offscreenCanvas = canvas2D.transferControlToOffscreen();
+  var worker = makeWorker(document.getElementById("myWorker").textContent);
+  worker.onmessage = function (e) {
+      waitForFinish();
+  };
+  worker.postMessage(offscreenCanvas, [offscreenCanvas]);
+}
+</script>
+</head>
+<body onload="main()">
+<div style="position:relative; width:200px; height:200px; background-color:white">
+</div>
+<div id="container" style="position:absolute; top:0px; left:0px">
+<canvas id="c" width="200" height="200" class="nomargin"></canvas>
+</div>
+</body>
+</html>
diff --git a/content/test/data/gpu/pixel_offscreenCanvas_webgl_resize_on_worker.html b/content/test/data/gpu/pixel_offscreenCanvas_webgl_resize_on_worker.html
new file mode 100644
index 0000000..ace65f3
--- /dev/null
+++ b/content/test/data/gpu/pixel_offscreenCanvas_webgl_resize_on_worker.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+
+<!-- READ BEFORE UPDATING:
+If this test is updated make sure to increment the "revision" value of the
+associated test in content/test/gpu/gpu_tests/pixel_test_pages.py. This will ensure
+that the baseline images are regenerated on the next run.
+-->
+
+<html>
+<head>
+<title>OffscreenCanvas webgl.commit with resizing: red triangle with softened edge.</title>
+<style type="text/css">
+.nomargin {
+  margin: 0px auto;
+}
+</style>
+<script id="myWorker" type="text/worker">
+function drawTriangle(gl)
+{
+  gl.clearColor(0, 1, 0, 1);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  var prog = gl.createProgram();
+  var vs = gl.createShader(gl.VERTEX_SHADER);
+  gl.shaderSource(vs, [
+    'attribute vec2 pos;', 'void main() {',
+    '  gl_Position = vec4(pos, 0., .5);', '}'
+  ].join('\n'));
+  gl.compileShader(vs);
+  if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
+    throw 'failed to compiled shader';
+  }
+  gl.attachShader(prog, vs);
+
+  var fs = gl.createShader(gl.FRAGMENT_SHADER);
+  gl.shaderSource(
+      fs, [ 'void main() {', '  gl_FragColor = vec4(1, 0, 0.5, 1);', '}' ].join('\n'));
+  gl.compileShader(fs);
+  if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
+    throw 'failed to compiled shader';
+  }
+  gl.attachShader(prog, fs);
+
+  gl.linkProgram(prog);
+  if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
+    throw "Could not link the shader program!";
+  }
+  gl.useProgram(prog);
+
+  gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -.5, .5, 0, 0, -.5, -.5 ]),
+                gl.STATIC_DRAW);
+  var attr = gl.getAttribLocation(prog, 'pos');
+  gl.enableVertexAttribArray(attr);
+  gl.vertexAttribPointer(attr, 2, gl.FLOAT, false, 0, 0);
+
+  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
+
+
+}
+
+self.onmessage = function(e) {
+  var transfered = e.data;
+  // Resize the canvas from 200X200 to 40X50
+  // The triangle should scale proportionately with edge softened
+  transfered.width = 40;
+  transfered.height = 50;
+
+  var gl = transfered.getContext('webgl');
+  drawTriangle(gl);
+  gl.commit();
+
+  self.postMessage("");
+};
+</script>
+<script>
+var g_swapsBeforeAck = 15;
+
+function makeWorker(script)
+{
+   var blob = new Blob([script]);
+   return new Worker(URL.createObjectURL(blob));
+}
+
+function waitForFinish()
+{
+  if (g_swapsBeforeAck == 0) {
+    domAutomationController.setAutomationId(1);
+    domAutomationController.send("SUCCESS");
+  } else {
+    g_swapsBeforeAck--;
+    document.getElementById('container').style.zIndex = g_swapsBeforeAck + 1;
+    window.webkitRequestAnimationFrame(waitForFinish);
+  }
+}
+
+function main()
+{
+  var canvas2D = document.getElementById("c");
+  var offscreenCanvas = canvas2D.transferControlToOffscreen();
+  var worker = makeWorker(document.getElementById("myWorker").textContent);
+  worker.onmessage = function (e) {
+      waitForFinish();
+  };
+  worker.postMessage(offscreenCanvas, [offscreenCanvas]);
+}
+</script>
+</head>
+<body onload="main()">
+<div style="position:relative; width:200px; height:200px; background-color:white">
+</div>
+<div id="container" style="position:absolute; top:0px; left:0px">
+<canvas id="c" width="200" height="200" class="nomargin"></canvas>
+</div>
+</body>
+</html>
diff --git a/content/test/gpu/gpu_tests/pixel_expectations.py b/content/test/gpu/gpu_tests/pixel_expectations.py
index 757260a..4f1b59a 100644
--- a/content/test/gpu/gpu_tests/pixel_expectations.py
+++ b/content/test/gpu/gpu_tests/pixel_expectations.py
@@ -24,6 +24,10 @@
     self.Fail('Pixel_ScissorTestWithPreserveDrawingBuffer',
         ['android'], bug=521588)
 
+    # TODO(xlai): Check / generate reference images.
+    self.Fail('Pixel_OffscreenCanvas2DResizeOnWorker', bug=662498)
+    self.Fail('Pixel_OffscreenCanvasWebglResizeOnWorker', bug=662498)
+
     # TODO(ccameron) fix these on Mac Retina
     self.Fail('Pixel_CSS3DBlueBox', ['mac'], bug=533690)
 
diff --git a/content/test/gpu/gpu_tests/pixel_test_pages.py b/content/test/gpu/gpu_tests/pixel_test_pages.py
index c3e2473..73d8c28 100644
--- a/content/test/gpu/gpu_tests/pixel_test_pages.py
+++ b/content/test/gpu/gpu_tests/pixel_test_pages.py
@@ -240,6 +240,20 @@
       browser_args=browser_args + ['--disable-accelerated-2d-canvas']),
 
     PixelTestPage(
+      'pixel_offscreenCanvas_2d_resize_on_worker.html',
+      base_name + '_OffscreenCanvas2DResizeOnWorker',
+      test_rect=[0, 0, 200, 200],
+      revision=1,
+      browser_args=browser_args),
+
+    PixelTestPage(
+      'pixel_offscreenCanvas_webgl_resize_on_worker.html',
+      base_name + '_OffscreenCanvasWebglResizeOnWorker',
+      test_rect=[0, 0, 200, 200],
+      revision=1,
+      browser_args=browser_args),
+
+    PixelTestPage(
       'pixel_canvas_display_linear-rgb.html',
       base_name + '_CanvasDisplayLinearRGBAccelerated2D',
       test_rect=[0, 0, 140, 140],
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 63ae0cc..68944bb 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -70,6 +70,65 @@
     self.Fail('conformance2/rendering/blitframebuffer-stencil-only.html',
         ['win', 'amd'], bug=483282) # owner:jmadill
 
+    # Failing due to the 16.12.1 driver upgrade
+    self.Fail('conformance2/textures/canvas/tex-3d-rgb16f-rgb-float.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('conformance2/textures/canvas/' +
+        'tex-2d-rgb8ui-rgb_integer-unsigned_byte.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('conformance2/textures/canvas_sub_rectangle/' +
+        'tex-3d-rgb8-rgb-unsigned_byte.html', ['win', 'amd', 'd3d11'],
+        bug=676333)
+    self.Fail('conformance2/textures/canvas_sub_rectangle/' +
+        'tex-3d-rgb565-rgb-unsigned_byte.html', ['win', 'amd', 'd3d11'],
+        bug=676333)
+    self.Fail('conformance2/textures/canvas_sub_rectangle/' +
+        'tex-3d-rgb565-rgb-unsigned_short_5_6_5.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('conformance2/textures/canvas_sub_rectangle/' +
+        'tex-3d-rgb32f-rgb-float.html', ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('conformance2/textures/canvas_sub_rectangle/' +
+        'tex-3d-rgb16f-rgb-half_float.html', ['win', 'amd', 'd3d11'],
+        bug=676333)
+    self.Fail('conformance2/textures/image/' +
+        'tex-3d-rgb565-rgb-unsigned_byte.html', ['win', 'amd', 'd3d11'],
+        bug=676333)
+    self.Fail('conformance2/textures/image/tex-3d-rgb32f-rgb-float.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('conformance2/textures/image/' +
+        'tex-3d-rgb565-rgb-unsigned_short_5_6_5.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('conformance2/textures/image/tex-3d-rgb16f-rgb-float.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('conformance2/textures/image/' +
+        'tex-3d-srgb8-rgb-unsigned_byte.html', ['win', 'amd', 'd3d11'],
+        bug=676333)
+    self.Fail('conformance2/textures/image_data/' +
+        'tex-3d-rgb16f-rgb-half_float.html', ['win', 'amd', 'd3d11'],
+        bug=676333)
+    self.Fail('conformance2/textures/image_data/tex-3d-rgb32f-rgb-float.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('conformance2/textures/video/tex-3d-rgb8-rgb-unsigned_byte.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('deqp/functional/gles3/texturespecification/' +
+        'basic_texsubimage3d_00.html', ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('deqp/functional/gles3/texturespecification/' +
+        'basic_texsubimage3d_01.html', ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('deqp/functional/gles3/texturespecification/' +
+        'basic_texsubimage3d_03.html', ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('deqp/functional/gles3/texturespecification/' +
+        'basic_teximage3d_3d_00.html', ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('deqp/functional/gles3/texturespecification/' +
+        'basic_teximage3d_3d_02.html', ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('deqp/functional/gles3/texturespecification/' +
+        'basic_teximage3d_3d_03.html', ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('deqp/functional/gles3/texturespecification/' +
+        'teximage3d_pbo_3d_01.html', ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('deqp/functional/gles3/texturefiltering/3d_formats_05.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+    self.Fail('conformance2/textures/misc/tex-unpack-params.html',
+        ['win', 'amd', 'd3d11'], bug=676333)
+
     # Failing on old R5 230 configuration.
     self.Fail('deqp/functional/gles3/shadertexturefunction/' +
         'texelfetchoffset.html',
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc
index c812ec83..02b50aed 100644
--- a/content/test/test_render_frame_host.cc
+++ b/content/test/test_render_frame_host.cc
@@ -409,9 +409,10 @@
   InitializeRenderFrameIfNeeded();
 
   if (IsBrowserSideNavigationEnabled()) {
-    BeginNavigationParams begin_params(std::string(), net::LOAD_NORMAL,
-                                       has_user_gesture, false,
-                                       REQUEST_CONTEXT_TYPE_HYPERLINK);
+    // TODO(mkwst): The initiator origin here is incorrect.
+    BeginNavigationParams begin_params(
+        std::string(), net::LOAD_NORMAL, has_user_gesture, false,
+        REQUEST_CONTEXT_TYPE_HYPERLINK, url::Origin());
     CommonNavigationParams common_params;
     common_params.url = url;
     common_params.referrer = Referrer(GURL(), blink::WebReferrerPolicyDefault);
diff --git a/extensions/common/api/_behavior_features.json b/extensions/common/api/_behavior_features.json
index a2da0de..e64975f 100644
--- a/extensions/common/api/_behavior_features.json
+++ b/extensions/common/api/_behavior_features.json
@@ -89,40 +89,5 @@
       "3C654B3B6682CA194E75AD044CEDE927675DDEE8",  // Easy unlock
       "2FCBCE08B34CCA1728A85F1EFBD9A34DD2558B2E"   // ChromeVox
     ]
-  }
-  ],
-  "whitelisted_for_networking_onc_api": {
-    "channel": "stable",
-    "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
-    "platforms": ["chromeos", "mac", "win", "linux"],
-    "whitelist": [
-      "0DE0F05680A4A056BCEC864ED8DDA84296F82B40",  // http://crbug.com/434651
-      "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80",  // http://crbug.com/293683
-      "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE",  // http://crbug.com/293683
-      "8C3741E3AF0B93B6E8E0DDD499BB0B74839EA578",  // http://crbug.com/234235
-      "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB",  // http://crbug.com/234235
-      "307E96539209F95A1A8740C713E6998A73657D96",  // http://crbug.com/329690
-      "11B478CEC461C766A2DC1E5BEEB7970AE06DC9C2",  // http://crbug.com/380890
-      "0EFB879311E9EFBB7C45251F89EC655711B1F6ED",  // http://crbug.com/380890
-      "9193D3A51E2FE33B496CDA53EA330423166E7F02",  // http://crbug.com/380890
-      "F9119B8B18C7C82B51E7BC6FF816B694F2EC3E89",  // http://crbug.com/380890
-      "63ED55E43214C211F82122ED56407FF1A807F2A3",  // Dev
-      "FA01E0B81978950F2BC5A50512FD769725F57510",  // Beta
-      "B11A93E7E5B541F8010245EBDE2C74647D6C14B9",  // Canary
-      "F155646B5D1CA545F7E1E4E20D573DFDD44C2540",  // Google Cast Beta
-      "16CA7A47AAE4BE49B1E75A6B960C3875E945B264",  // Google Cast Stable
-      "226CF815E39A363090A1E547D53063472B8279FA",  // http://crbug.com/588179
-      "7AE714FFD394E073F0294CFA134C9F91DB5FBAA4",  // CCD Development
-      "C7DA3A55C2355F994D3FDDAD120B426A0DF63843",  // CCD Testing
-      "75E3CFFFC530582C583E4690EF97C70B9C8423B7",  // CCD Release
-      "4F25792AF1AA7483936DE29C07806F203C7170A0",  // http://crbug.com/407693
-      "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9",  // http://crbug.com/407693
-      "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB",  // http://crbug.com/407693
-      "81986D4F846CEDDDB962643FA501D1780DD441BB",  // http://crbug.com/407693
-      "2F6F6FDB84E0290ABAB7A9D7571EB344821E5F12",  // http://crbug.com/610452
-      "5B9E39EA374B136CBE7AED2D872003107642EAD5",  // http://crbug.com/610452
-      "E0E94FB0C01FFB9CDC7A5F098C99B5A8D2F95902",  // http://crbug.com/610452
-      "52E0557059A7A28F74ED1D92DDD997E0CCD37806"   // http://crbug.com/610452
-    ]
-  }
+  }]
 }
diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json
index 9365997..dc7d3cbd 100644
--- a/extensions/common/api/_permission_features.json
+++ b/extensions/common/api/_permission_features.json
@@ -310,12 +310,74 @@
   },
   "networking.onc": {
     "channel": "dev",
-    "dependencies": ["behavior:whitelisted_for_networking_onc_api"],
-    "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+    "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+    "platforms": ["chromeos", "mac", "win", "linux"],
+    "whitelist": [
+      "0DE0F05680A4A056BCEC864ED8DDA84296F82B40",  // http://crbug.com/434651
+      "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80",  // http://crbug.com/293683
+      "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE",  // http://crbug.com/293683
+      "8C3741E3AF0B93B6E8E0DDD499BB0B74839EA578",  // http://crbug.com/234235
+      "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB",  // http://crbug.com/234235
+      "307E96539209F95A1A8740C713E6998A73657D96",  // http://crbug.com/329690
+      "11B478CEC461C766A2DC1E5BEEB7970AE06DC9C2",  // http://crbug.com/380890
+      "0EFB879311E9EFBB7C45251F89EC655711B1F6ED",  // http://crbug.com/380890
+      "9193D3A51E2FE33B496CDA53EA330423166E7F02",  // http://crbug.com/380890
+      "F9119B8B18C7C82B51E7BC6FF816B694F2EC3E89",  // http://crbug.com/380890
+      "63ED55E43214C211F82122ED56407FF1A807F2A3",  // Dev
+      "FA01E0B81978950F2BC5A50512FD769725F57510",  // Beta
+      "B11A93E7E5B541F8010245EBDE2C74647D6C14B9",  // Canary
+      "F155646B5D1CA545F7E1E4E20D573DFDD44C2540",  // Google Cast Beta
+      "16CA7A47AAE4BE49B1E75A6B960C3875E945B264",  // Google Cast Stable
+      "226CF815E39A363090A1E547D53063472B8279FA",  // http://crbug.com/588179
+      "7AE714FFD394E073F0294CFA134C9F91DB5FBAA4",  // CCD Development
+      "C7DA3A55C2355F994D3FDDAD120B426A0DF63843",  // CCD Testing
+      "75E3CFFFC530582C583E4690EF97C70B9C8423B7",  // CCD Release
+      "4F25792AF1AA7483936DE29C07806F203C7170A0",  // http://crbug.com/407693
+      "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9",  // http://crbug.com/407693
+      "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB",  // http://crbug.com/407693
+      "81986D4F846CEDDDB962643FA501D1780DD441BB",  // http://crbug.com/407693
+      "2F6F6FDB84E0290ABAB7A9D7571EB344821E5F12",  // http://crbug.com/610452
+      "5B9E39EA374B136CBE7AED2D872003107642EAD5",  // http://crbug.com/610452
+      "E0E94FB0C01FFB9CDC7A5F098C99B5A8D2F95902",  // http://crbug.com/610452
+      "52E0557059A7A28F74ED1D92DDD997E0CCD37806"   // http://crbug.com/610452
+    ]
   },
   "networkingPrivate": {
-    "dependencies": ["behavior:whitelisted_for_networking_onc_api"],
-    "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+    "channel": "stable",
+    "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+    "platforms": ["chromeos", "mac", "win", "linux"],
+    "whitelist": [
+      // DO NOT ADD ANY MORE ENTRIES HERE.
+      // networkingPrivate is being migrated to networking.onc.
+      // TODO(tbarzic): Remove this API.
+      "0DE0F05680A4A056BCEC864ED8DDA84296F82B40",  // http://crbug.com/434651
+      "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80",  // http://crbug.com/293683
+      "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE",  // http://crbug.com/293683
+      "8C3741E3AF0B93B6E8E0DDD499BB0B74839EA578",  // http://crbug.com/234235
+      "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB",  // http://crbug.com/234235
+      "307E96539209F95A1A8740C713E6998A73657D96",  // http://crbug.com/329690
+      "11B478CEC461C766A2DC1E5BEEB7970AE06DC9C2",  // http://crbug.com/380890
+      "0EFB879311E9EFBB7C45251F89EC655711B1F6ED",  // http://crbug.com/380890
+      "9193D3A51E2FE33B496CDA53EA330423166E7F02",  // http://crbug.com/380890
+      "F9119B8B18C7C82B51E7BC6FF816B694F2EC3E89",  // http://crbug.com/380890
+      "63ED55E43214C211F82122ED56407FF1A807F2A3",  // Dev
+      "FA01E0B81978950F2BC5A50512FD769725F57510",  // Beta
+      "B11A93E7E5B541F8010245EBDE2C74647D6C14B9",  // Canary
+      "F155646B5D1CA545F7E1E4E20D573DFDD44C2540",  // Google Cast Beta
+      "16CA7A47AAE4BE49B1E75A6B960C3875E945B264",  // Google Cast Stable
+      "226CF815E39A363090A1E547D53063472B8279FA",  // http://crbug.com/588179
+      "7AE714FFD394E073F0294CFA134C9F91DB5FBAA4",  // CCD Development
+      "C7DA3A55C2355F994D3FDDAD120B426A0DF63843",  // CCD Testing
+      "75E3CFFFC530582C583E4690EF97C70B9C8423B7",  // CCD Release
+      "4F25792AF1AA7483936DE29C07806F203C7170A0",  // http://crbug.com/407693
+      "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9",  // http://crbug.com/407693
+      "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB",  // http://crbug.com/407693
+      "81986D4F846CEDDDB962643FA501D1780DD441BB",  // http://crbug.com/407693
+      "2F6F6FDB84E0290ABAB7A9D7571EB344821E5F12",  // http://crbug.com/610452
+      "5B9E39EA374B136CBE7AED2D872003107642EAD5",  // http://crbug.com/610452
+      "E0E94FB0C01FFB9CDC7A5F098C99B5A8D2F95902",  // http://crbug.com/610452
+      "52E0557059A7A28F74ED1D92DDD997E0CCD37806"   // http://crbug.com/610452
+    ]
   },
   "power": {
     "channel": "stable",
diff --git a/extensions/common/extension_api.cc b/extensions/common/extension_api.cc
index 26ca193..a32b095 100644
--- a/extensions/common/extension_api.cc
+++ b/extensions/common/extension_api.cc
@@ -157,7 +157,7 @@
 }
 
 void ExtensionAPI::InitDefaultConfiguration() {
-  const char* names[] = {"api", "behavior", "manifest", "permission"};
+  const char* names[] = {"api", "manifest", "permission"};
   for (size_t i = 0; i < arraysize(names); ++i)
     RegisterDependencyProvider(names[i], FeatureProvider::GetByName(names[i]));
 
diff --git a/extensions/common/features/behavior_feature.cc b/extensions/common/features/behavior_feature.cc
index 1248276..519b829 100644
--- a/extensions/common/features/behavior_feature.cc
+++ b/extensions/common/features/behavior_feature.cc
@@ -19,9 +19,6 @@
 
 const char kSigninScreen[] = "signin_screen";
 
-const char kWhitelistedForNetworkingOncApi[] =
-    "whitelisted_for_networking_onc_api";
-
 }  // namespace behavior_feature
 
 }  // namespace extensions
diff --git a/extensions/common/features/behavior_feature.h b/extensions/common/features/behavior_feature.h
index bf799a5e..190cf7d 100644
--- a/extensions/common/features/behavior_feature.h
+++ b/extensions/common/features/behavior_feature.h
@@ -16,7 +16,6 @@
 extern const char kZoomWithoutBubble[];
 extern const char kAllowUsbDevicesPermissionInterfaceClass[];
 extern const char kSigninScreen[];
-extern const char kWhitelistedForNetworkingOncApi[];
 
 }  // namespace behavior_feature
 
diff --git a/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc b/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
index 5d6ace6..1c005eba 100644
--- a/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
+++ b/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
@@ -295,6 +295,12 @@
     DCHECK(!ShouldSkipTest());
     return !gl_.decoder()->GetFeatureInfo()->feature_flags().ext_srgb;
   }
+
+  // RGB5_A1 is not color-renderable on NVIDIA Mac, see crbug.com/676209.
+  bool ShouldSkipRGB5_A1() const {
+    DCHECK(!ShouldSkipTest());
+    return true;
+  }
 };
 
 INSTANTIATE_TEST_CASE_P(CopyType,
@@ -425,6 +431,8 @@
            dest_format_type.internal_format == GL_SRGB_ALPHA_EXT) &&
           ShouldSkipSRGBEXT())
         continue;
+      if (dest_format_type.internal_format == GL_RGB5_A1 && ShouldSkipRGB5_A1())
+        continue;
 
       const GLsizei kWidth = 8, kHeight = 8;
       const int src_channel_count = gles2::GLES2Util::ElementsPerGroup(
diff --git a/ios/chrome/browser/ntp_snippets/ios_chrome_content_suggestions_service_factory.cc b/ios/chrome/browser/ntp_snippets/ios_chrome_content_suggestions_service_factory.cc
index 3dd4b40..232edcbd 100644
--- a/ios/chrome/browser/ntp_snippets/ios_chrome_content_suggestions_service_factory.cc
+++ b/ios/chrome/browser/ntp_snippets/ios_chrome_content_suggestions_service_factory.cc
@@ -24,9 +24,11 @@
 #include "components/ntp_snippets/features.h"
 #include "components/ntp_snippets/ntp_snippets_constants.h"
 #include "components/ntp_snippets/remote/ntp_snippets_fetcher.h"
+#include "components/ntp_snippets/remote/persistent_scheduler.h"
 #include "components/ntp_snippets/remote/remote_suggestions_database.h"
-#include "components/ntp_snippets/remote/remote_suggestions_provider.h"
+#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
 #include "components/ntp_snippets/remote/remote_suggestions_status_service.h"
+#include "components/ntp_snippets/remote/scheduling_remote_suggestions_provider.h"
 #include "components/signin/core/browser/signin_manager.h"
 #include "components/version_info/version_info.h"
 #include "google_apis/google_api_keys.h"
@@ -49,10 +51,11 @@
 using ntp_snippets::BookmarkSuggestionsProvider;
 using ntp_snippets::ContentSuggestionsService;
 using ntp_snippets::NTPSnippetsFetcher;
-using ntp_snippets::NTPSnippetsScheduler;
+using ntp_snippets::PersistentScheduler;
 using ntp_snippets::RemoteSuggestionsDatabase;
-using ntp_snippets::RemoteSuggestionsProvider;
+using ntp_snippets::RemoteSuggestionsProviderImpl;
 using ntp_snippets::RemoteSuggestionsStatusService;
+using ntp_snippets::SchedulingRemoteSuggestionsProvider;
 using suggestions::CreateIOSImageDecoder;
 using suggestions::ImageFetcherImpl;
 
@@ -147,7 +150,6 @@
         OAuth2TokenServiceFactory::GetForBrowserState(chrome_browser_state);
     scoped_refptr<net::URLRequestContextGetter> request_context =
         browser_state->GetRequestContext();
-    NTPSnippetsScheduler* scheduler = nullptr;
     base::FilePath database_dir(
         browser_state->GetStatePath().Append(ntp_snippets::kDatabaseFolder));
     scoped_refptr<base::SequencedTaskRunner> task_runner =
@@ -155,27 +157,32 @@
             ->GetSequencedTaskRunnerWithShutdownBehavior(
                 base::SequencedWorkerPool::GetSequenceToken(),
                 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
-    std::unique_ptr<RemoteSuggestionsProvider> ntp_snippets_service =
-        base::MakeUnique<RemoteSuggestionsProvider>(
-            service.get(), prefs,
-            GetApplicationContext()->GetApplicationLocale(),
-            service->category_ranker(), service->user_classifier(), scheduler,
-            base::MakeUnique<NTPSnippetsFetcher>(
-                signin_manager, token_service, request_context, prefs, nullptr,
-                base::Bind(&ParseJson),
-                GetChannel() == version_info::Channel::STABLE
-                    ? google_apis::GetAPIKey()
-                    : google_apis::GetNonStableAPIKey(),
-                service->user_classifier()),
-            base::MakeUnique<ImageFetcherImpl>(
-                request_context.get(), web::WebThread::GetBlockingPool()),
-            CreateIOSImageDecoder(task_runner),
-            base::MakeUnique<RemoteSuggestionsDatabase>(database_dir,
-                                                        task_runner),
-            base::MakeUnique<RemoteSuggestionsStatusService>(signin_manager,
-                                                             prefs));
-    service->set_ntp_snippets_service(ntp_snippets_service.get());
-    service->RegisterProvider(std::move(ntp_snippets_service));
+    auto provider = base::MakeUnique<RemoteSuggestionsProviderImpl>(
+        service.get(), prefs, GetApplicationContext()->GetApplicationLocale(),
+        service->category_ranker(),
+        base::MakeUnique<NTPSnippetsFetcher>(
+            signin_manager, token_service, request_context, prefs, nullptr,
+            base::Bind(&ParseJson),
+            GetChannel() == version_info::Channel::STABLE
+                ? google_apis::GetAPIKey()
+                : google_apis::GetNonStableAPIKey(),
+            service->user_classifier()),
+        base::MakeUnique<ImageFetcherImpl>(request_context.get(),
+                                           web::WebThread::GetBlockingPool()),
+        CreateIOSImageDecoder(task_runner),
+        base::MakeUnique<RemoteSuggestionsDatabase>(database_dir, task_runner),
+        base::MakeUnique<RemoteSuggestionsStatusService>(signin_manager,
+                                                         prefs));
+
+    // TODO(jkrcal): Implement a persistent scheduler for iOS. crbug.com/676249
+    auto scheduling_provider =
+        base::MakeUnique<SchedulingRemoteSuggestionsProvider>(
+            service.get(), std::move(provider),
+            /*persistent_scheduler=*/nullptr, service->user_classifier(),
+            prefs);
+    service->set_remote_suggestions_provider(scheduling_provider.get());
+    service->set_remote_suggestions_scheduler(scheduling_provider.get());
+    service->RegisterProvider(std::move(scheduling_provider));
   }
 
   return std::move(service);
diff --git a/ios/chrome/browser/prefs/browser_prefs.mm b/ios/chrome/browser/prefs/browser_prefs.mm
index 80cab2eb..64950c4 100644
--- a/ios/chrome/browser/prefs/browser_prefs.mm
+++ b/ios/chrome/browser/prefs/browser_prefs.mm
@@ -14,7 +14,8 @@
 #include "components/network_time/network_time_tracker.h"
 #include "components/ntp_snippets/bookmarks/bookmark_suggestions_provider.h"
 #include "components/ntp_snippets/content_suggestions_service.h"
-#include "components/ntp_snippets/remote/remote_suggestions_provider.h"
+#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
+#include "components/ntp_snippets/remote/scheduling_remote_suggestions_provider.h"
 #include "components/ntp_tiles/most_visited_sites.h"
 #include "components/ntp_tiles/popular_sites_impl.h"
 #include "components/omnibox/browser/zero_suggest_provider.h"
@@ -90,8 +91,10 @@
   HostContentSettingsMap::RegisterProfilePrefs(registry);
   HttpServerPropertiesManagerFactory::RegisterProfilePrefs(registry);
   ntp_snippets::BookmarkSuggestionsProvider::RegisterProfilePrefs(registry);
-  ntp_snippets::RemoteSuggestionsProvider::RegisterProfilePrefs(registry);
   ntp_snippets::ContentSuggestionsService::RegisterProfilePrefs(registry);
+  ntp_snippets::RemoteSuggestionsProviderImpl::RegisterProfilePrefs(registry);
+  ntp_snippets::SchedulingRemoteSuggestionsProvider::RegisterProfilePrefs(
+      registry);
   ntp_tiles::MostVisitedSites::RegisterProfilePrefs(registry);
   ntp_tiles::PopularSitesImpl::RegisterProfilePrefs(registry);
   ios::NotificationPromo::RegisterProfilePrefs(registry);
diff --git a/ios/chrome/browser/ui/reading_list/BUILD.gn b/ios/chrome/browser/ui/reading_list/BUILD.gn
index 924867c..2d489285 100644
--- a/ios/chrome/browser/ui/reading_list/BUILD.gn
+++ b/ios/chrome/browser/ui/reading_list/BUILD.gn
@@ -45,6 +45,8 @@
     "reading_list_view_controller.mm",
     "reading_list_view_controller_builder.h",
     "reading_list_view_controller_builder.mm",
+    "reading_list_view_controller_container.h",
+    "reading_list_view_controller_container.mm",
   ]
   deps = [
     "//base",
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller.h b/ios/chrome/browser/ui/reading_list/reading_list_view_controller.h
index 5aaa994..145be12 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_view_controller.h
+++ b/ios/chrome/browser/ui/reading_list/reading_list_view_controller.h
@@ -19,6 +19,15 @@
 class ReadingListModel;
 @class TabModel;
 
+// Audience for the ReadingListViewController, managing the visibility of the
+// toolbar.
+@protocol ReadingListViewControllerAudience<NSObject>
+
+// Whether the collection has items.
+- (void)setCollectionHasItems:(BOOL)hasItems;
+
+@end
+
 @interface ReadingListViewController
     : CollectionViewController<ReadingListToolbarDelegate>
 
@@ -33,10 +42,13 @@
               largeIconService:(favicon::LargeIconService*)largeIconService
     readingListDownloadService:
         (ReadingListDownloadService*)readingListDownloadService
+                       toolbar:(ReadingListToolbar*)toolbar
     NS_DESIGNATED_INITIALIZER;
 - (instancetype)initWithStyle:(CollectionViewControllerStyle)style
     NS_UNAVAILABLE;
 
+@property(nonatomic, weak) id<ReadingListViewControllerAudience> audience;
+
 @end
 
 @interface ReadingListViewController (Testing)
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm b/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm
index c7e64f0..7ee1123 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm
@@ -51,9 +51,6 @@
 NSString* const kEmptyReadingListShareIcon = @"share_icon";
 NSString* const kShareIconMarker = @"SHARE_ICON";
 
-// Height of the toolbar.
-const int kToolbarHeight = 48;
-
 // Background view constants.
 const CGFloat kTextImageSpacing = 10;
 const CGFloat kTextHorizontalMargin = 32;
@@ -112,8 +109,6 @@
 // Returns whether there are elements in the section identified by
 // |sectionIdentifier|.
 - (BOOL)hasItemInSection:(SectionIdentifier)sectionIdentifier;
-// Adds the bottom toolbar with the edition options.
-- (void)addToolbar;
 // Updates the toolbar state according to the selected items.
 - (void)updateToolbarState;
 // Displays an action sheet to let the user choose to mark all the elements as
@@ -173,6 +168,7 @@
 @synthesize largeIconService = _largeIconService;
 @synthesize readingListDownloadService = _readingListDownloadService;
 @synthesize attributesProvider = _attributesProvider;
+@synthesize audience = _audience;
 
 #pragma mark lifecycle
 
@@ -180,21 +176,19 @@
                       tabModel:(TabModel*)tabModel
               largeIconService:(favicon::LargeIconService*)largeIconService
     readingListDownloadService:
-        (ReadingListDownloadService*)readingListDownloadService {
+        (ReadingListDownloadService*)readingListDownloadService
+                       toolbar:(ReadingListToolbar*)toolbar {
   self = [super initWithStyle:CollectionViewControllerStyleAppBar];
   if (self) {
     DCHECK(model);
-
-    // Configure modal presentation.
-    [self setModalPresentationStyle:UIModalPresentationFormSheet];
-    [self setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
+    _toolbar = toolbar;
+    _toolbar.delegate = self;
 
     _readingListModel = model;
     _tabModel = tabModel;
     _largeIconService = largeIconService;
     _readingListDownloadService = readingListDownloadService;
     _emptyCollectionBackground = [self emptyCollectionBackground];
-    _toolbar = [[ReadingListToolbar alloc] initWithFrame:CGRectZero];
 
     _modelBridge.reset(new ReadingListModelBridge(self, model));
   }
@@ -219,20 +213,16 @@
   [_toolbar setState:toolbarState];
 }
 
-#pragma mark - UIViewController
-
-- (void)updateViewConstraints {
-  NSDictionary* views = @{ @"toolbar" : _toolbar };
-  NSDictionary* metrics = @{ @"toolbarHeight" : @(kToolbarHeight) };
-  NSArray* constraints =
-      @[ @"V:[toolbar(==toolbarHeight)]|", @"H:|[toolbar]|" ];
-  ApplyVisualConstraintsWithMetrics(constraints, views, metrics);
-  [super updateViewConstraints];
+- (void)setAudience:(id<ReadingListViewControllerAudience>)audience {
+  _audience = audience;
+  if (self.readingListModel->loaded())
+    [audience setCollectionHasItems:self.readingListModel->size() > 0];
 }
 
+#pragma mark - UIViewController
+
 - (void)viewDidLoad {
   [super viewDidLoad];
-  [self addToolbar];
 
   self.title = l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_READING_LIST);
 
@@ -456,11 +446,11 @@
   if (self.readingListModel->size() == 0) {
     // The collection is empty, add background.
     self.collectionView.backgroundView = _emptyCollectionBackground;
-    [_toolbar setHidden:YES];
+    [self.audience setCollectionHasItems:NO];
   } else {
     [self loadItems];
     self.collectionView.backgroundView = nil;
-    [_toolbar setHidden:NO];
+    [self.audience setCollectionHasItems:YES];
   }
 }
 
@@ -574,16 +564,6 @@
 
 #pragma mark - Private methods - Toolbar
 
-- (void)addToolbar {
-  [_toolbar setDelegate:self];
-  [_toolbar setTranslatesAutoresizingMaskIntoConstraints:NO];
-  [self.view addSubview:_toolbar];
-  UIEdgeInsets insets = self.collectionView.contentInset;
-  insets.bottom += kToolbarHeight + 8;
-  self.collectionView.contentInset = insets;
-  [_toolbar setHidden:YES];
-}
-
 - (void)updateToolbarState {
   BOOL readSelected = NO;
   BOOL unreadSelected = NO;
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_builder.h b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_builder.h
index 7e7a87d..5528784 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_builder.h
+++ b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_builder.h
@@ -7,7 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
-#import "ios/chrome/browser/ui/reading_list/reading_list_view_controller.h"
+#import "ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.h"
 
 namespace ios {
 class ChromeBrowserState;
@@ -20,7 +20,7 @@
 // A builder class that constructs ReadingListViewControllers.
 @interface ReadingListViewControllerBuilder : NSObject
 
-+ (ReadingListViewController*)
++ (ReadingListViewControllerContainer*)
 readingListViewControllerInBrowserState:(ios::ChromeBrowserState*)browserState
                                tabModel:(TabModel*)tabModel;
 
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_builder.mm b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_builder.mm
index d626bb3..8a9f776b 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_builder.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_builder.mm
@@ -18,7 +18,7 @@
 
 @implementation ReadingListViewControllerBuilder
 
-+ (ReadingListViewController*)
++ (ReadingListViewControllerContainer*)
 readingListViewControllerInBrowserState:(ios::ChromeBrowserState*)browserState
                                tabModel:(TabModel*)tabModel {
   ReadingListModel* model =
@@ -28,11 +28,11 @@
   ReadingListDownloadService* rlservice =
       ReadingListDownloadServiceFactory::GetInstance()->GetForBrowserState(
           browserState);
-  ReadingListViewController* vc =
-      [[ReadingListViewController alloc] initWithModel:model
-                                              tabModel:tabModel
-                                      largeIconService:service
-                            readingListDownloadService:rlservice];
+  ReadingListViewControllerContainer* vc =
+      [[ReadingListViewControllerContainer alloc] initWithModel:model
+                                                       tabModel:tabModel
+                                               largeIconService:service
+                                     readingListDownloadService:rlservice];
   return vc;
 }
 
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.h b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.h
new file mode 100644
index 0000000..a3ca190
--- /dev/null
+++ b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.h
@@ -0,0 +1,35 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_VIEW_CONTROLLER_CONTAINER_H_
+#define IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_VIEW_CONTROLLER_CONTAINER_H_
+
+#import "ios/chrome/browser/ui/reading_list/reading_list_view_controller.h"
+
+namespace favicon {
+class LargeIconService;
+}
+
+class ReadingListDownloadService;
+class ReadingListModel;
+@class TabModel;
+
+// Container for the Reading List View Controller.
+@interface ReadingListViewControllerContainer
+    : UIViewController<ReadingListViewControllerAudience>
+
+- (instancetype)initWithModel:(ReadingListModel*)model
+                      tabModel:(TabModel*)tabModel
+              largeIconService:(favicon::LargeIconService*)largeIconService
+    readingListDownloadService:
+        (ReadingListDownloadService*)readingListDownloadService
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithNibName:(NSString*)nibNameOrNil
+                         bundle:(NSBundle*)nibBundleOrNil NS_UNAVAILABLE;
+- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_VIEW_CONTROLLER_CONTAINER_H_
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.mm b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.mm
new file mode 100644
index 0000000..76524b7e
--- /dev/null
+++ b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.mm
@@ -0,0 +1,90 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.h"
+
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#import "ios/chrome/browser/ui/reading_list/reading_list_view_controller.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+// Height of the toolbar.
+const int kToolbarHeight = 48;
+}
+
+@implementation ReadingListViewControllerContainer {
+  // Toolbar with the actions.
+  ReadingListToolbar* _toolbar;
+  ReadingListViewController* _collectionController;
+}
+
+- (instancetype)initWithModel:(ReadingListModel*)model
+                      tabModel:(TabModel*)tabModel
+              largeIconService:(favicon::LargeIconService*)largeIconService
+    readingListDownloadService:
+        (ReadingListDownloadService*)readingListDownloadService {
+  self = [super initWithNibName:nil bundle:nil];
+  if (self) {
+    _toolbar = [[ReadingListToolbar alloc] initWithFrame:CGRectZero];
+    _collectionController = [[ReadingListViewController alloc]
+                     initWithModel:model
+                          tabModel:tabModel
+                  largeIconService:largeIconService
+        readingListDownloadService:readingListDownloadService
+                           toolbar:_toolbar];
+    _collectionController.audience = self;
+
+    // Configure modal presentation.
+    [self setModalPresentationStyle:UIModalPresentationFormSheet];
+    [self setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
+  }
+  return self;
+}
+
+- (void)viewDidLoad {
+  [self addChildViewController:_collectionController];
+  [self.view addSubview:[_collectionController view]];
+  [_collectionController didMoveToParentViewController:self];
+
+  [_toolbar setTranslatesAutoresizingMaskIntoConstraints:NO];
+  [[_collectionController view]
+      setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+  NSDictionary* views = @{ @"collection" : [_collectionController view] };
+  NSArray* constraints = @[ @"V:|[collection]", @"H:|[collection]|" ];
+  ApplyVisualConstraints(constraints, views);
+
+  // This constraints is not required. It will be activated only when the
+  // toolbar is not present, allowing the collection to take the whole page.
+  NSLayoutConstraint* constraint = [[_collectionController view].bottomAnchor
+      constraintEqualToAnchor:[self view].bottomAnchor];
+  constraint.priority = UILayoutPriorityDefaultHigh;
+  constraint.active = YES;
+}
+
+#pragma mark - ReadingListViewControllerDelegate
+
+- (void)setCollectionHasItems:(BOOL)hasItems {
+  if (hasItems) {
+    // If there are items, add the toolbar.
+    [self.view addSubview:_toolbar];
+    NSDictionary* views = @{
+      @"toolbar" : _toolbar,
+      @"collection" : [_collectionController view]
+    };
+    NSDictionary* metrics = @{ @"toolbarHeight" : @(kToolbarHeight) };
+    NSArray* constraints =
+        @[ @"V:[collection][toolbar(==toolbarHeight)]|", @"H:|[toolbar]|" ];
+    ApplyVisualConstraintsWithMetrics(constraints, views, metrics);
+  } else {
+    // If there is no item, remove the toolbar. The constraints will make sure
+    // the collection takes the whole view.
+    [_toolbar removeFromSuperview];
+  }
+}
+
+@end
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_unittest.mm b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_unittest.mm
index bf3cc8c1..8ea20799 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_unittest.mm
@@ -103,7 +103,8 @@
                      initWithModel:reading_list_model_.get()
                           tabModel:tabModel
                   largeIconService:large_icon_service_.get()
-        readingListDownloadService:nil]);
+        readingListDownloadService:nil
+                           toolbar:nil]);
   }
 
   DISALLOW_COPY_AND_ASSIGN(ReadingListViewControllerTest);
diff --git a/ios/web/web_state/js/resources/message.js b/ios/web/web_state/js/resources/message.js
index 47d32a78..96dc9ad2 100644
--- a/ios/web/web_state/js/resources/message.js
+++ b/ios/web/web_state/js/resources/message.js
@@ -77,17 +77,15 @@
       delete Object.prototype.toJSON;
 
     queueObject.queue.forEach(function(command) {
-        var stringifiedMessage = __gCrWeb.common.JSONStringify({
-            "crwCommand": command,
-            "crwWindowId": __gCrWeb['windowId']
-        });
         // A web page can override |window.webkit| with any value. Deleting the
         // object ensures that original and working implementation of
         // window.webkit is restored.
         var oldWebkit = window.webkit;
         delete window['webkit'];
-        window.webkit.messageHandlers[queueObject.scheme].postMessage(
-            stringifiedMessage);
+        window.webkit.messageHandlers[queueObject.scheme].postMessage({
+            "crwCommand": command,
+            "crwWindowId": __gCrWeb['windowId']
+        });
         window.webkit = oldWebkit;
     });
     queueObject.reset();
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index a77b4048..9862aab 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -19,7 +19,6 @@
 #include "base/ios/ios_util.h"
 #import "base/ios/ns_error_util.h"
 #include "base/ios/weak_nsobject.h"
-#include "base/json/json_reader.h"
 #include "base/json/string_escape.h"
 #include "base/logging.h"
 #include "base/mac/bind_objc_block.h"
@@ -2698,18 +2697,10 @@
     return NO;
   }
 
-  int errorCode = 0;
-  std::string errorMessage;
-  std::unique_ptr<base::Value> inputJSONData(
-      base::JSONReader::ReadAndReturnError(
-          base::SysNSStringToUTF8(scriptMessage.body), false, &errorCode,
-          &errorMessage));
-  if (errorCode) {
-    DLOG(WARNING) << "JSON parse error: %s" << errorMessage.c_str();
-    return NO;
-  }
+  std::unique_ptr<base::Value> messageAsValue =
+      web::ValueResultFromWKResult(scriptMessage.body);
   base::DictionaryValue* message = nullptr;
-  if (!inputJSONData->GetAsDictionary(&message)) {
+  if (!messageAsValue || !messageAsValue->GetAsDictionary(&message)) {
     return NO;
   }
   std::string windowID;
@@ -2921,8 +2912,12 @@
     inputMissing = true;
   }
 
-  if (!message->GetInteger("keyCode", &keyCode) || keyCode < 0)
+  double keyCodeAsDouble = 0;
+  if (!message->GetDouble("keyCode", &keyCodeAsDouble) || keyCodeAsDouble < 0) {
     keyCode = web::WebStateObserver::kInvalidFormKeyCode;
+  } else {
+    keyCode = static_cast<int>(keyCodeAsDouble);
+  }
   _webStateImpl->OnFormActivityRegistered(formName, fieldName, type, value,
                                           keyCode, inputMissing);
   return YES;
@@ -2930,8 +2925,8 @@
 
 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
                                   context:(NSDictionary*)context {
-  int request_id = -1;
-  if (!message->GetInteger("requestId", &request_id)) {
+  double request_id = -1;
+  if (!message->GetDouble("requestId", &request_id)) {
     DLOG(WARNING) << "JS message parameter not found: requestId";
     return NO;
   }
@@ -2956,15 +2951,16 @@
   }
   DCHECK(context[web::kUserIsInteractingKey]);
   _webStateImpl->OnCredentialsRequested(
-      request_id, net::GURLWithNSURL(context[web::kOriginURLKey]), unmediated,
-      federations, [context[web::kUserIsInteractingKey] boolValue]);
+      static_cast<int>(request_id),
+      net::GURLWithNSURL(context[web::kOriginURLKey]), unmediated, federations,
+      [context[web::kUserIsInteractingKey] boolValue]);
   return YES;
 }
 
 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
                       context:(NSDictionary*)context {
-  int request_id = -1;
-  if (!message->GetInteger("requestId", &request_id)) {
+  double request_id = -1;
+  if (!message->GetDouble("requestId", &request_id)) {
     DLOG(WARNING) << "JS message parameter not found: requestId";
     return NO;
   }
@@ -2975,11 +2971,11 @@
       DLOG(WARNING) << "JS message parameter 'credential' is invalid";
       return NO;
     }
-    _webStateImpl->OnSignedIn(request_id,
+    _webStateImpl->OnSignedIn(static_cast<int>(request_id),
                               net::GURLWithNSURL(context[web::kOriginURLKey]),
                               credential);
   } else {
-    _webStateImpl->OnSignedIn(request_id,
+    _webStateImpl->OnSignedIn(static_cast<int>(request_id),
                               net::GURLWithNSURL(context[web::kOriginURLKey]));
   }
   return YES;
@@ -2987,20 +2983,20 @@
 
 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
                        context:(NSDictionary*)context {
-  int request_id = -1;
-  if (!message->GetInteger("requestId", &request_id)) {
+  double request_id = -1;
+  if (!message->GetDouble("requestId", &request_id)) {
     DLOG(WARNING) << "JS message parameter not found: requestId";
     return NO;
   }
-  _webStateImpl->OnSignedOut(request_id,
+  _webStateImpl->OnSignedOut(static_cast<int>(request_id),
                              net::GURLWithNSURL(context[web::kOriginURLKey]));
   return YES;
 }
 
 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
                           context:(NSDictionary*)context {
-  int request_id = -1;
-  if (!message->GetInteger("requestId", &request_id)) {
+  double request_id = -1;
+  if (!message->GetDouble("requestId", &request_id)) {
     DLOG(WARNING) << "JS message parameter not found: requestId";
     return NO;
   }
@@ -3012,11 +3008,12 @@
       return NO;
     }
     _webStateImpl->OnSignInFailed(
-        request_id, net::GURLWithNSURL(context[web::kOriginURLKey]),
-        credential);
+        static_cast<int>(request_id),
+        net::GURLWithNSURL(context[web::kOriginURLKey]), credential);
   } else {
     _webStateImpl->OnSignInFailed(
-        request_id, net::GURLWithNSURL(context[web::kOriginURLKey]));
+        static_cast<int>(request_id),
+        net::GURLWithNSURL(context[web::kOriginURLKey]));
   }
   return YES;
 }
@@ -3077,9 +3074,9 @@
 
 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
                              context:(NSDictionary*)context {
-  int delta = 0;
-  if (message->GetInteger("value", &delta)) {
-    [self goDelta:delta];
+  double delta = 0;
+  if (message->GetDouble("value", &delta)) {
+    [self goDelta:static_cast<int>(delta)];
     return YES;
   }
   return NO;
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py
index f5444a0..78af649 100755
--- a/mojo/public/tools/bindings/mojom_bindings_generator.py
+++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -196,7 +196,7 @@
       with open(rel_filename.path) as f:
         source = f.read()
     except IOError as e:
-      print "%s: Error: %s" % (e.rel_filename.path, e.strerror) + \
+      print "%s: Error: %s" % (rel_filename.path, e.strerror) + \
           MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
       sys.exit(1)
 
diff --git a/net/http/disk_cache_based_quic_server_info.h b/net/http/disk_cache_based_quic_server_info.h
index ebdd65f..1ee59d2 100644
--- a/net/http/disk_cache_based_quic_server_info.h
+++ b/net/http/disk_cache_based_quic_server_info.h
@@ -15,7 +15,7 @@
 #include "net/base/completion_callback.h"
 #include "net/base/net_export.h"
 #include "net/disk_cache/disk_cache.h"
-#include "net/quic/core/crypto/quic_server_info.h"
+#include "net/quic/chromium/quic_server_info.h"
 
 namespace net {
 
diff --git a/net/http/disk_cache_based_quic_server_info_unittest.cc b/net/http/disk_cache_based_quic_server_info_unittest.cc
index 569b865..d4d338e 100644
--- a/net/http/disk_cache_based_quic_server_info_unittest.cc
+++ b/net/http/disk_cache_based_quic_server_info_unittest.cc
@@ -12,7 +12,7 @@
 #include "base/run_loop.h"
 #include "net/base/net_errors.h"
 #include "net/http/mock_http_cache.h"
-#include "net/quic/core/crypto/quic_server_info.h"
+#include "net/quic/chromium/quic_server_info.h"
 #include "net/quic/core/quic_server_id.h"
 #include "net/test/gtest_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index 54cf571..bab16394 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -43,7 +43,7 @@
 #include "net/http/http_response_info.h"
 #include "net/http/http_util.h"
 #include "net/log/net_log_with_source.h"
-#include "net/quic/core/crypto/quic_server_info.h"
+#include "net/quic/chromium/quic_server_info.h"
 
 #if defined(OS_POSIX)
 #include <unistd.h>
diff --git a/net/http2/hpack/decoder/hpack_whole_entry_buffer.cc b/net/http2/hpack/decoder/hpack_whole_entry_buffer.cc
new file mode 100644
index 0000000..8bb212f
--- /dev/null
+++ b/net/http2/hpack/decoder/hpack_whole_entry_buffer.cc
@@ -0,0 +1,135 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+
+#include "base/logging.h"
+
+using base::StringPiece;
+
+namespace net {
+
+HpackWholeEntryBuffer::HpackWholeEntryBuffer(HpackWholeEntryListener* listener,
+                                             size_t max_string_size_bytes)
+    : max_string_size_bytes_(max_string_size_bytes) {
+  set_listener(listener);
+}
+HpackWholeEntryBuffer::~HpackWholeEntryBuffer() {}
+
+void HpackWholeEntryBuffer::set_listener(HpackWholeEntryListener* listener) {
+  CHECK(listener);
+  listener_ = listener;
+}
+
+void HpackWholeEntryBuffer::set_max_string_size_bytes(
+    size_t max_string_size_bytes) {
+  max_string_size_bytes_ = max_string_size_bytes;
+}
+
+void HpackWholeEntryBuffer::BufferStringsIfUnbuffered() {
+  name_.BufferStringIfUnbuffered();
+  value_.BufferStringIfUnbuffered();
+}
+
+void HpackWholeEntryBuffer::OnIndexedHeader(size_t index) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnIndexedHeader: index=" << index;
+  listener_->OnIndexedHeader(index);
+}
+
+void HpackWholeEntryBuffer::OnStartLiteralHeader(HpackEntryType entry_type,
+                                                 size_t maybe_name_index) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnStartLiteralHeader: entry_type="
+           << entry_type << ",  maybe_name_index=" << maybe_name_index;
+  entry_type_ = entry_type;
+  maybe_name_index_ = maybe_name_index;
+}
+
+void HpackWholeEntryBuffer::OnNameStart(bool huffman_encoded, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnNameStart: huffman_encoded="
+           << (huffman_encoded ? "true" : "false") << ",  len=" << len;
+  DCHECK_EQ(maybe_name_index_, 0u);
+  if (!error_detected_) {
+    if (len > max_string_size_bytes_) {
+      DVLOG(1) << "Name length (" << len << ") is longer than permitted ("
+               << max_string_size_bytes_ << ")";
+      ReportError("HPACK entry name size is too long.");
+      return;
+    }
+    name_.OnStart(huffman_encoded, len);
+  }
+}
+
+void HpackWholeEntryBuffer::OnNameData(const char* data, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnNameData: len=" << len
+           << "\n data: " << StringPiece(data, len);
+  DCHECK_EQ(maybe_name_index_, 0u);
+  if (!error_detected_ && !name_.OnData(data, len)) {
+    ReportError("Error decoding HPACK entry name.");
+  }
+}
+
+void HpackWholeEntryBuffer::OnNameEnd() {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnNameEnd";
+  DCHECK_EQ(maybe_name_index_, 0u);
+  if (!error_detected_ && !name_.OnEnd()) {
+    ReportError("Error decoding HPACK entry name.");
+  }
+}
+
+void HpackWholeEntryBuffer::OnValueStart(bool huffman_encoded, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnValueStart: huffman_encoded="
+           << (huffman_encoded ? "true" : "false") << ",  len=" << len;
+  if (!error_detected_) {
+    if (len > max_string_size_bytes_) {
+      DVLOG(1) << "Value length (" << len << ") is longer than permitted ("
+               << max_string_size_bytes_ << ")";
+      ReportError("HPACK entry value size is too long.");
+      return;
+    }
+    value_.OnStart(huffman_encoded, len);
+  }
+}
+
+void HpackWholeEntryBuffer::OnValueData(const char* data, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnValueData: len=" << len
+           << "\n data: " << StringPiece(data, len);
+  if (!error_detected_ && !value_.OnData(data, len)) {
+    ReportError("Error decoding HPACK entry value.");
+  }
+}
+
+void HpackWholeEntryBuffer::OnValueEnd() {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnValueEnd";
+  if (error_detected_) {
+    return;
+  }
+  if (!value_.OnEnd()) {
+    ReportError("Error decoding HPACK entry value.");
+    return;
+  }
+  if (maybe_name_index_ == 0) {
+    listener_->OnLiteralNameAndValue(entry_type_, &name_, &value_);
+    name_.Reset();
+  } else {
+    listener_->OnNameIndexAndLiteralValue(entry_type_, maybe_name_index_,
+                                          &value_);
+  }
+  value_.Reset();
+}
+
+void HpackWholeEntryBuffer::OnDynamicTableSizeUpdate(size_t size) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnDynamicTableSizeUpdate: size=" << size;
+  listener_->OnDynamicTableSizeUpdate(size);
+}
+
+void HpackWholeEntryBuffer::ReportError(StringPiece error_message) {
+  if (!error_detected_) {
+    DVLOG(1) << "HpackWholeEntryBuffer::ReportError: " << error_message;
+    error_detected_ = true;
+    listener_->OnHpackDecodeError(error_message);
+    listener_ = HpackWholeEntryNoOpListener::NoOpListener();
+  }
+}
+
+}  // namespace net
diff --git a/net/http2/hpack/decoder/hpack_whole_entry_buffer.h b/net/http2/hpack/decoder/hpack_whole_entry_buffer.h
new file mode 100644
index 0000000..8ab5d693
--- /dev/null
+++ b/net/http2/hpack/decoder/hpack_whole_entry_buffer.h
@@ -0,0 +1,101 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
+#define NET_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
+
+// HpackWholeEntryBuffer isolates a listener from the fact that an entry may
+// be split across multiple input buffers, providing one callback per entry.
+// HpackWholeEntryBuffer requires that the HpackEntryDecoderListener be made in
+// the correct order, which is tested by hpack_entry_decoder_test.cc.
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/http2/hpack/decoder/hpack_whole_entry_listener.h"
+#include "net/http2/hpack/http2_hpack_constants.h"
+
+namespace net {
+
+// TODO(jamessynge): Consider renaming HpackEntryDecoderListener to
+// HpackEntryPartsListener or HpackEntryFragmentsListener.
+class NET_EXPORT_PRIVATE HpackWholeEntryBuffer
+    : public HpackEntryDecoderListener {
+ public:
+  // max_string_size specifies the maximum size of an on-the-wire string (name
+  // or value, plain or Huffman encoded) that will be accepted. See sections
+  // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2
+  // allows a decoder to enforce any limit of the size of the header lists
+  // that it is willing decode, including less than the MAX_HEADER_LIST_SIZE
+  // setting, a setting that is initially unlimited. For example, we might
+  // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value
+  // as the upper bound for individual strings.
+  HpackWholeEntryBuffer(HpackWholeEntryListener* listener,
+                        size_t max_string_size);
+  ~HpackWholeEntryBuffer() override;
+
+  // Set the listener to be notified when a whole entry has been decoded.
+  // The listener may be changed at any time.
+  void set_listener(HpackWholeEntryListener* listener);
+
+  // Set how much encoded data this decoder is willing to buffer.
+  // TODO(jamessynge): Come up with consistent semantics for this protection
+  // across the various decoders; e.g. should it be for a single string or
+  // a single header entry?
+  void set_max_string_size_bytes(size_t max_string_size_bytes);
+
+  // Ensure that decoded strings pointed to by the HpackDecoderStringBuffer
+  // instances name_ and value_ are buffered, which allows any underlying
+  // transport buffer to be freed or reused without overwriting the decoded
+  // strings. This is needed only when an HPACK entry is split across transport
+  // buffers. See HpackDecoder::DecodeFragment.
+  void BufferStringsIfUnbuffered();
+
+  // Was an error detected? After an error has been detected and reported,
+  // no further callbacks will be made to the listener.
+  bool error_detected() const { return error_detected_; }
+
+  // Implement the HpackEntryDecoderListener methods.
+
+  void OnIndexedHeader(size_t index) override;
+  void OnStartLiteralHeader(HpackEntryType entry_type,
+                            size_t maybe_name_index) override;
+  void OnNameStart(bool huffman_encoded, size_t len) override;
+  void OnNameData(const char* data, size_t len) override;
+  void OnNameEnd() override;
+  void OnValueStart(bool huffman_encoded, size_t len) override;
+  void OnValueData(const char* data, size_t len) override;
+  void OnValueEnd() override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+
+ private:
+  void ReportError(base::StringPiece error_message);
+
+  HpackWholeEntryListener* listener_;
+  HpackDecoderStringBuffer name_, value_;
+
+  // max_string_size_bytes_ specifies the maximum allowed size of an on-the-wire
+  // string. Larger strings will be reported as errors to the listener; the
+  // endpoint should treat these as COMPRESSION errors, which are CONNECTION
+  // level errors.
+  size_t max_string_size_bytes_;
+
+  // The name index (or zero) of the current header entry with a literal value.
+  size_t maybe_name_index_;
+
+  // The type of the current header entry (with literals) that is being decoded.
+  HpackEntryType entry_type_;
+
+  bool error_detected_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(HpackWholeEntryBuffer);
+};
+
+}  // namespace net
+
+#endif  // NET_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
diff --git a/net/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc b/net/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc
new file mode 100644
index 0000000..e062046
--- /dev/null
+++ b/net/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc
@@ -0,0 +1,221 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+
+// Tests of HpackWholeEntryBuffer: does it buffer correctly, and does it
+// detect Huffman decoding errors and oversize string errors?
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+using ::testing::InSequence;
+using ::testing::Property;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+using ::testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+constexpr size_t kMaxStringSize = 20;
+
+class MockHpackWholeEntryListener : public HpackWholeEntryListener {
+ public:
+  ~MockHpackWholeEntryListener() override {}
+
+  MOCK_METHOD1(OnIndexedHeader, void(size_t index));
+  MOCK_METHOD3(OnNameIndexAndLiteralValue,
+               void(HpackEntryType entry_type,
+                    size_t name_index,
+                    HpackDecoderStringBuffer* value_buffer));
+  MOCK_METHOD3(OnLiteralNameAndValue,
+               void(HpackEntryType entry_type,
+                    HpackDecoderStringBuffer* name_buffer,
+                    HpackDecoderStringBuffer* value_buffer));
+  MOCK_METHOD1(OnDynamicTableSizeUpdate, void(size_t size));
+  MOCK_METHOD1(OnHpackDecodeError, void(StringPiece error_message));
+};
+
+class HpackWholeEntryBufferTest : public ::testing::Test {
+ protected:
+  HpackWholeEntryBufferTest() : entry_buffer_(&listener_, kMaxStringSize) {}
+  ~HpackWholeEntryBufferTest() override {}
+
+  StrictMock<MockHpackWholeEntryListener> listener_;
+  HpackWholeEntryBuffer entry_buffer_;
+};
+
+// OnIndexedHeader is an immediate pass through.
+TEST_F(HpackWholeEntryBufferTest, OnIndexedHeader) {
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(17));
+    entry_buffer_.OnIndexedHeader(17);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(62));
+    entry_buffer_.OnIndexedHeader(62);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(62));
+    entry_buffer_.OnIndexedHeader(62);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(128));
+    entry_buffer_.OnIndexedHeader(128);
+  }
+  StrictMock<MockHpackWholeEntryListener> listener2;
+  entry_buffer_.set_listener(&listener2);
+  {
+    InSequence seq;
+    EXPECT_CALL(listener2, OnIndexedHeader(100));
+    entry_buffer_.OnIndexedHeader(100);
+  }
+}
+
+// OnDynamicTableSizeUpdate is an immediate pass through.
+TEST_F(HpackWholeEntryBufferTest, OnDynamicTableSizeUpdate) {
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(4096));
+    entry_buffer_.OnDynamicTableSizeUpdate(4096);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(0));
+    entry_buffer_.OnDynamicTableSizeUpdate(0);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024));
+    entry_buffer_.OnDynamicTableSizeUpdate(1024);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024));
+    entry_buffer_.OnDynamicTableSizeUpdate(1024);
+  }
+  StrictMock<MockHpackWholeEntryListener> listener2;
+  entry_buffer_.set_listener(&listener2);
+  {
+    InSequence seq;
+    EXPECT_CALL(listener2, OnDynamicTableSizeUpdate(0));
+    entry_buffer_.OnDynamicTableSizeUpdate(0);
+  }
+}
+
+TEST_F(HpackWholeEntryBufferTest, OnNameIndexAndLiteralValue) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader,
+                                     123);
+  entry_buffer_.OnValueStart(false, 10);
+  entry_buffer_.OnValueData("some data.", 10);
+
+  // Force the value to be buffered.
+  entry_buffer_.BufferStringsIfUnbuffered();
+
+  EXPECT_CALL(
+      listener_,
+      OnNameIndexAndLiteralValue(
+          HpackEntryType::kNeverIndexedLiteralHeader, 123,
+          AllOf(Property(&HpackDecoderStringBuffer::str, "some data."),
+                Property(&HpackDecoderStringBuffer::BufferedLength, 10))));
+
+  entry_buffer_.OnValueEnd();
+}
+
+TEST_F(HpackWholeEntryBufferTest, OnLiteralNameAndValue) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0);
+  // Force the name to be buffered by delivering it in two pieces.
+  entry_buffer_.OnNameStart(false, 9);
+  entry_buffer_.OnNameData("some-", 5);
+  entry_buffer_.OnNameData("name", 4);
+  entry_buffer_.OnNameEnd();
+  entry_buffer_.OnValueStart(false, 12);
+  entry_buffer_.OnValueData("Header Value", 12);
+
+  EXPECT_CALL(
+      listener_,
+      OnLiteralNameAndValue(
+          HpackEntryType::kIndexedLiteralHeader,
+          AllOf(Property(&HpackDecoderStringBuffer::str, "some-name"),
+                Property(&HpackDecoderStringBuffer::BufferedLength, 9)),
+          AllOf(Property(&HpackDecoderStringBuffer::str, "Header Value"),
+                Property(&HpackDecoderStringBuffer::BufferedLength, 0))));
+
+  entry_buffer_.OnValueEnd();
+}
+
+// Verify that a name longer than the allowed size generates an error.
+TEST_F(HpackWholeEntryBufferTest, NameTooLong) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0);
+  StringPiece error_message;
+  EXPECT_CALL(listener_, OnHpackDecodeError(_))
+      .WillOnce(SaveArg<0>(&error_message));
+  entry_buffer_.OnNameStart(false, kMaxStringSize + 1);
+  EXPECT_THAT(error_message.as_string(), HasSubstr("HPACK entry name"));
+}
+
+// Verify that a name longer than the allowed size generates an error.
+TEST_F(HpackWholeEntryBufferTest, ValueTooLong) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 1);
+  StringPiece error_message;
+  EXPECT_CALL(listener_, OnHpackDecodeError(_))
+      .WillOnce(SaveArg<0>(&error_message));
+  entry_buffer_.OnValueStart(false, kMaxStringSize + 1);
+  EXPECT_THAT(error_message.as_string(), HasSubstr("HPACK entry value"));
+}
+
+// Verify that a Huffman encoded name with an explicit EOS generates an error
+// for an explicit EOS.
+TEST_F(HpackWholeEntryBufferTest, NameHuffmanError) {
+  const char data[] = "\xff\xff\xff";
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kUnindexedLiteralHeader,
+                                     0);
+  entry_buffer_.OnNameStart(true, 4);
+  entry_buffer_.OnNameData(data, 3);
+
+  StringPiece error_message;
+  EXPECT_CALL(listener_, OnHpackDecodeError(_))
+      .WillOnce(SaveArg<0>(&error_message));
+
+  entry_buffer_.OnNameData(data, 1);
+  EXPECT_THAT(error_message.as_string(), HasSubstr("HPACK entry name"));
+
+  // After an error is reported, the listener is not called again.
+  EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(8096)).Times(0);
+  entry_buffer_.OnDynamicTableSizeUpdate(8096);
+}
+
+// Verify that a Huffman encoded value that isn't properly terminated with
+// a partial EOS symbol generates an error.
+TEST_F(HpackWholeEntryBufferTest, ValueeHuffmanError) {
+  const char data[] = "\x00\x00\x00";
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader,
+                                     61);
+  entry_buffer_.OnValueStart(true, 3);
+  entry_buffer_.OnValueData(data, 3);
+
+  StringPiece error_message;
+  EXPECT_CALL(listener_, OnHpackDecodeError(_))
+      .WillOnce(SaveArg<0>(&error_message));
+
+  entry_buffer_.OnValueEnd();
+  EXPECT_THAT(error_message.as_string(), HasSubstr("HPACK entry value"));
+
+  // After an error is reported, the listener is not called again.
+  EXPECT_CALL(listener_, OnIndexedHeader(17)).Times(0);
+  entry_buffer_.OnIndexedHeader(17);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/http2/hpack/decoder/hpack_whole_entry_listener.cc b/net/http2/hpack/decoder/hpack_whole_entry_listener.cc
new file mode 100644
index 0000000..7962ec2
--- /dev/null
+++ b/net/http2/hpack/decoder/hpack_whole_entry_listener.cc
@@ -0,0 +1,37 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/http2/hpack/decoder/hpack_whole_entry_listener.h"
+
+#include "base/logging.h"
+
+using base::StringPiece;
+
+namespace net {
+
+HpackWholeEntryListener::~HpackWholeEntryListener() {}
+
+HpackWholeEntryNoOpListener::~HpackWholeEntryNoOpListener() {}
+
+void HpackWholeEntryNoOpListener::OnIndexedHeader(size_t index) {}
+void HpackWholeEntryNoOpListener::OnNameIndexAndLiteralValue(
+    HpackEntryType entry_type,
+    size_t name_index,
+    HpackDecoderStringBuffer* value_buffer) {}
+void HpackWholeEntryNoOpListener::OnLiteralNameAndValue(
+    HpackEntryType entry_type,
+    HpackDecoderStringBuffer* name_buffer,
+    HpackDecoderStringBuffer* value_buffer) {}
+void HpackWholeEntryNoOpListener::OnDynamicTableSizeUpdate(size_t size) {}
+void HpackWholeEntryNoOpListener::OnHpackDecodeError(
+    StringPiece error_message) {}
+
+// static
+HpackWholeEntryNoOpListener* HpackWholeEntryNoOpListener::NoOpListener() {
+  static HpackWholeEntryNoOpListener* static_instance =
+      new HpackWholeEntryNoOpListener();
+  return static_instance;
+}
+
+}  // namespace net
diff --git a/net/http2/hpack/decoder/hpack_whole_entry_listener.h b/net/http2/hpack/decoder/hpack_whole_entry_listener.h
new file mode 100644
index 0000000..af1457ed
--- /dev/null
+++ b/net/http2/hpack/decoder/hpack_whole_entry_listener.h
@@ -0,0 +1,80 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines HpackWholeEntryListener, the base class of listeners for decoded
+// complete HPACK entries, as opposed to HpackEntryDecoderListener which
+// receives multiple callbacks for some single entries.
+
+#ifndef NET_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
+#define NET_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
+
+#include <stddef.h>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/http2/hpack/http2_hpack_constants.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE HpackWholeEntryListener {
+ public:
+  virtual ~HpackWholeEntryListener();
+
+  // Called when an indexed header (i.e. one in the static or dynamic table) has
+  // been decoded from an HPACK block. index is supposed to be non-zero, but
+  // that has not been checked by the caller.
+  virtual void OnIndexedHeader(size_t index) = 0;
+
+  // Called when a header entry with a name index and literal value has
+  // been fully decoded from an HPACK block. name_index is NOT zero.
+  // entry_type will be kIndexedLiteralHeader, kUnindexedLiteralHeader, or
+  // kNeverIndexedLiteralHeader.
+  virtual void OnNameIndexAndLiteralValue(
+      HpackEntryType entry_type,
+      size_t name_index,
+      HpackDecoderStringBuffer* value_buffer) = 0;
+
+  // Called when a header entry with a literal name and literal value
+  // has been fully decoded from an HPACK block. entry_type will be
+  // kIndexedLiteralHeader, kUnindexedLiteralHeader, or
+  // kNeverIndexedLiteralHeader.
+  virtual void OnLiteralNameAndValue(
+      HpackEntryType entry_type,
+      HpackDecoderStringBuffer* name_buffer,
+      HpackDecoderStringBuffer* value_buffer) = 0;
+
+  // Called when an update to the size of the peer's dynamic table has been
+  // decoded.
+  virtual void OnDynamicTableSizeUpdate(size_t size) = 0;
+
+  // OnHpackDecodeError is called if an error is detected while decoding.
+  // error_message may be used in a GOAWAY frame as the Opaque Data.
+  virtual void OnHpackDecodeError(base::StringPiece error_message) = 0;
+};
+
+// A no-op implementation of HpackWholeEntryDecoderListener, useful for ignoring
+// callbacks once an error is detected.
+class HpackWholeEntryNoOpListener : public HpackWholeEntryListener {
+ public:
+  ~HpackWholeEntryNoOpListener() override;
+
+  void OnIndexedHeader(size_t index) override;
+  void OnNameIndexAndLiteralValue(
+      HpackEntryType entry_type,
+      size_t name_index,
+      HpackDecoderStringBuffer* value_buffer) override;
+  void OnLiteralNameAndValue(HpackEntryType entry_type,
+                             HpackDecoderStringBuffer* name_buffer,
+                             HpackDecoderStringBuffer* value_buffer) override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+  void OnHpackDecodeError(base::StringPiece error_message) override;
+
+  // Returns a listener that ignores all the calls.
+  static HpackWholeEntryNoOpListener* NoOpListener();
+};
+
+}  // namespace net
+
+#endif  // NET_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
diff --git a/net/net.gypi b/net/net.gypi
index 6832ffb..c69b8b3 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -838,6 +838,10 @@
       'http2/hpack/decoder/hpack_string_decoder_listener.h',
       'http2/hpack/decoder/hpack_varint_decoder.cc',
       'http2/hpack/decoder/hpack_varint_decoder.h',
+      'http2/hpack/decoder/hpack_whole_entry_buffer.cc',
+      'http2/hpack/decoder/hpack_whole_entry_buffer.h',
+      'http2/hpack/decoder/hpack_whole_entry_listener.cc',
+      'http2/hpack/decoder/hpack_whole_entry_listener.h',
       'http2/hpack/hpack_string.cc',
       'http2/hpack/hpack_string.h',
       'http2/hpack/http2_hpack_constants.cc',
@@ -948,6 +952,8 @@
       'quic/chromium/network_connection.h',
       'quic/chromium/quic_address_mismatch.cc',
       'quic/chromium/quic_address_mismatch.h',
+      'quic/chromium/properties_based_quic_server_info.cc',
+      'quic/chromium/properties_based_quic_server_info.h',
       'quic/chromium/quic_chromium_alarm_factory.cc',
       'quic/chromium/quic_chromium_alarm_factory.h',
       'quic/chromium/quic_chromium_client_session.cc',
@@ -970,6 +976,8 @@
       'quic/chromium/quic_http_stream.h',
       'quic/chromium/quic_http_utils.cc',
       'quic/chromium/quic_http_utils.h',
+      'quic/chromium/quic_server_info.cc',
+      'quic/chromium/quic_server_info.h',
       'quic/chromium/quic_stream_factory.cc',
       'quic/chromium/quic_stream_factory.h',
       'quic/chromium/quic_utils_chromium.h',
@@ -1047,8 +1055,6 @@
       'quic/core/crypto/proof_source.cc',
       'quic/core/crypto/proof_source.h',
       'quic/core/crypto/proof_verifier.h',
-      'quic/core/crypto/properties_based_quic_server_info.cc',
-      'quic/core/crypto/properties_based_quic_server_info.h',
       'quic/core/crypto/quic_compressed_certs_cache.cc',
       'quic/core/crypto/quic_compressed_certs_cache.h',
       'quic/core/crypto/quic_crypto_client_config.cc',
@@ -1063,8 +1069,6 @@
       'quic/core/crypto/quic_encrypter.h',
       'quic/core/crypto/quic_random.cc',
       'quic/core/crypto/quic_random.h',
-      'quic/core/crypto/quic_server_info.cc',
-      'quic/core/crypto/quic_server_info.h',
       'quic/core/crypto/scoped_evp_aead_ctx.cc',
       'quic/core/crypto/scoped_evp_aead_ctx.h',
       'quic/core/crypto/strike_register.cc',
@@ -1796,6 +1800,7 @@
       'http2/hpack/decoder/hpack_string_collector.h',
       'http2/hpack/decoder/hpack_string_decoder_test.cc',
       'http2/hpack/decoder/hpack_varint_decoder_test.cc',
+      'http2/hpack/decoder/hpack_whole_entry_buffer_test.cc',
       'http2/hpack/hpack_string_test.cc',
       'http2/hpack/http2_hpack_constants_test.cc',
       'http2/hpack/huffman/http2_hpack_huffman_decoder_test.cc',
@@ -1866,6 +1871,7 @@
       'quic/chromium/mock_quic_data.h',
       'quic/chromium/network_connection_unittest.cc',
       'quic/chromium/quic_address_mismatch_test.cc',
+      'quic/chromium/properties_based_quic_server_info_test.cc',
       'quic/chromium/quic_chromium_alarm_factory_test.cc',
       'quic/chromium/quic_chromium_client_session_peer.cc',
       'quic/chromium/quic_chromium_client_session_peer.h',
@@ -1911,7 +1917,6 @@
       'quic/core/crypto/null_decrypter_test.cc',
       'quic/core/crypto/null_encrypter_test.cc',
       'quic/core/crypto/p256_key_exchange_test.cc',
-      'quic/core/crypto/properties_based_quic_server_info_test.cc',
       'quic/core/crypto/quic_compressed_certs_cache_test.cc',
       'quic/core/crypto/quic_crypto_client_config_test.cc',
       'quic/core/crypto/quic_crypto_server_config_test.cc',
diff --git a/net/quic/chromium/bidirectional_stream_quic_impl_unittest.cc b/net/quic/chromium/bidirectional_stream_quic_impl_unittest.cc
index c2f20489..5da56649 100644
--- a/net/quic/chromium/bidirectional_stream_quic_impl_unittest.cc
+++ b/net/quic/chromium/bidirectional_stream_quic_impl_unittest.cc
@@ -26,11 +26,11 @@
 #include "net/quic/chromium/quic_chromium_packet_reader.h"
 #include "net/quic/chromium/quic_chromium_packet_writer.h"
 #include "net/quic/chromium/quic_http_utils.h"
+#include "net/quic/chromium/quic_server_info.h"
 #include "net/quic/chromium/quic_test_packet_maker.h"
 #include "net/quic/core/crypto/crypto_protocol.h"
 #include "net/quic/core/crypto/quic_decrypter.h"
 #include "net/quic/core/crypto/quic_encrypter.h"
-#include "net/quic/core/crypto/quic_server_info.h"
 #include "net/quic/core/quic_connection.h"
 #include "net/quic/core/spdy_utils.h"
 #include "net/quic/test_tools/crypto_test_utils.h"
diff --git a/net/quic/core/crypto/properties_based_quic_server_info.cc b/net/quic/chromium/properties_based_quic_server_info.cc
similarity index 97%
rename from net/quic/core/crypto/properties_based_quic_server_info.cc
rename to net/quic/chromium/properties_based_quic_server_info.cc
index 0ab4f6ce..3881454 100644
--- a/net/quic/core/crypto/properties_based_quic_server_info.cc
+++ b/net/quic/chromium/properties_based_quic_server_info.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/quic/core/crypto/properties_based_quic_server_info.h"
+#include "net/quic/chromium/properties_based_quic_server_info.h"
 
 #include "base/base64.h"
 #include "base/metrics/histogram_macros.h"
diff --git a/net/quic/core/crypto/properties_based_quic_server_info.h b/net/quic/chromium/properties_based_quic_server_info.h
similarity index 87%
rename from net/quic/core/crypto/properties_based_quic_server_info.h
rename to net/quic/chromium/properties_based_quic_server_info.h
index 21fcf7fc..6fe9f78d 100644
--- a/net/quic/core/crypto/properties_based_quic_server_info.h
+++ b/net/quic/chromium/properties_based_quic_server_info.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 NET_QUIC_CORE_CRYPTO_PROPERTIES_BASED_QUIC_SERVER_INFO_H_
-#define NET_QUIC_CORE_CRYPTO_PROPERTIES_BASED_QUIC_SERVER_INFO_H_
+#ifndef NET_QUIC_CHROMIUM_PROPERTIES_BASED_QUIC_SERVER_INFO_H_
+#define NET_QUIC_CHROMIUM_PROPERTIES_BASED_QUIC_SERVER_INFO_H_
 
 #include <string>
 #include <vector>
@@ -11,7 +11,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "net/base/completion_callback.h"
-#include "net/quic/core/crypto/quic_server_info.h"
+#include "net/quic/chromium/quic_server_info.h"
 #include "net/quic/platform/api/quic_export.h"
 
 namespace net {
@@ -61,4 +61,4 @@
 
 }  // namespace net
 
-#endif  // NET_QUIC_CORE_CRYPTO_PROPERTIES_BASED_QUIC_SERVER_INFO_H_
+#endif  // NET_QUIC_CHROMIUM_PROPERTIES_BASED_QUIC_SERVER_INFO_H_
diff --git a/net/quic/core/crypto/properties_based_quic_server_info_test.cc b/net/quic/chromium/properties_based_quic_server_info_test.cc
similarity index 97%
rename from net/quic/core/crypto/properties_based_quic_server_info_test.cc
rename to net/quic/chromium/properties_based_quic_server_info_test.cc
index 747905f1..aba7c635 100644
--- a/net/quic/core/crypto/properties_based_quic_server_info_test.cc
+++ b/net/quic/chromium/properties_based_quic_server_info_test.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/quic/core/crypto/properties_based_quic_server_info.h"
+#include "net/quic/chromium/properties_based_quic_server_info.h"
 
 #include <string>
 
diff --git a/net/quic/chromium/quic_chromium_client_session.cc b/net/quic/chromium/quic_chromium_client_session.cc
index 3bf85988..9cffc527 100644
--- a/net/quic/chromium/quic_chromium_client_session.cc
+++ b/net/quic/chromium/quic_chromium_client_session.cc
@@ -27,8 +27,8 @@
 #include "net/quic/chromium/quic_chromium_connection_helper.h"
 #include "net/quic/chromium/quic_chromium_packet_writer.h"
 #include "net/quic/chromium/quic_crypto_client_stream_factory.h"
+#include "net/quic/chromium/quic_server_info.h"
 #include "net/quic/chromium/quic_stream_factory.h"
-#include "net/quic/core/crypto/quic_server_info.h"
 #include "net/quic/core/quic_client_promised_info.h"
 #include "net/quic/core/spdy_utils.h"
 #include "net/socket/datagram_client_socket.h"
diff --git a/net/quic/chromium/quic_chromium_client_session_test.cc b/net/quic/chromium/quic_chromium_client_session_test.cc
index 3a9e56f..77f6400 100644
--- a/net/quic/chromium/quic_chromium_client_session_test.cc
+++ b/net/quic/chromium/quic_chromium_client_session_test.cc
@@ -25,12 +25,12 @@
 #include "net/quic/chromium/quic_chromium_packet_writer.h"
 #include "net/quic/chromium/quic_crypto_client_stream_factory.h"
 #include "net/quic/chromium/quic_http_utils.h"
+#include "net/quic/chromium/quic_server_info.h"
 #include "net/quic/chromium/quic_test_packet_maker.h"
 #include "net/quic/core/crypto/aes_128_gcm_12_encrypter.h"
 #include "net/quic/core/crypto/crypto_protocol.h"
 #include "net/quic/core/crypto/quic_decrypter.h"
 #include "net/quic/core/crypto/quic_encrypter.h"
-#include "net/quic/core/crypto/quic_server_info.h"
 #include "net/quic/core/quic_client_promised_info.h"
 #include "net/quic/core/quic_flags.h"
 #include "net/quic/core/quic_packet_writer.h"
diff --git a/net/quic/chromium/quic_http_stream_test.cc b/net/quic/chromium/quic_http_stream_test.cc
index 1dbeaa58..3915b81 100644
--- a/net/quic/chromium/quic_http_stream_test.cc
+++ b/net/quic/chromium/quic_http_stream_test.cc
@@ -33,12 +33,12 @@
 #include "net/quic/chromium/quic_chromium_packet_reader.h"
 #include "net/quic/chromium/quic_chromium_packet_writer.h"
 #include "net/quic/chromium/quic_http_utils.h"
+#include "net/quic/chromium/quic_server_info.h"
 #include "net/quic/chromium/quic_test_packet_maker.h"
 #include "net/quic/core/congestion_control/send_algorithm_interface.h"
 #include "net/quic/core/crypto/crypto_protocol.h"
 #include "net/quic/core/crypto/quic_decrypter.h"
 #include "net/quic/core/crypto/quic_encrypter.h"
-#include "net/quic/core/crypto/quic_server_info.h"
 #include "net/quic/core/quic_connection.h"
 #include "net/quic/core/quic_write_blocked_list.h"
 #include "net/quic/core/spdy_utils.h"
diff --git a/net/quic/core/crypto/quic_server_info.cc b/net/quic/chromium/quic_server_info.cc
similarity index 98%
rename from net/quic/core/crypto/quic_server_info.cc
rename to net/quic/chromium/quic_server_info.cc
index fa5be4a..7259dbac 100644
--- a/net/quic/core/crypto/quic_server_info.cc
+++ b/net/quic/chromium/quic_server_info.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/quic/core/crypto/quic_server_info.h"
+#include "net/quic/chromium/quic_server_info.h"
 
 #include <limits>
 
diff --git a/net/quic/core/crypto/quic_server_info.h b/net/quic/chromium/quic_server_info.h
similarity index 97%
rename from net/quic/core/crypto/quic_server_info.h
rename to net/quic/chromium/quic_server_info.h
index f214f2c0..34c6f1e 100644
--- a/net/quic/core/crypto/quic_server_info.h
+++ b/net/quic/chromium/quic_server_info.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 NET_QUIC_CORE_CRYPTO_QUIC_SERVER_INFO_H_
-#define NET_QUIC_CORE_CRYPTO_QUIC_SERVER_INFO_H_
+#ifndef NET_QUIC_CHROMIUM_QUIC_SERVER_INFO_H_
+#define NET_QUIC_CHROMIUM_QUIC_SERVER_INFO_H_
 
 #include <string>
 #include <vector>
@@ -175,4 +175,4 @@
 
 }  // namespace net
 
-#endif  // NET_QUIC_CORE_CRYPTO_QUIC_SERVER_INFO_H_
+#endif  // NET_QUIC_CHROMIUM_QUIC_SERVER_INFO_H_
diff --git a/net/quic/chromium/quic_stream_factory.cc b/net/quic/chromium/quic_stream_factory.cc
index 42b1195c..48cc2a6 100644
--- a/net/quic/chromium/quic_stream_factory.cc
+++ b/net/quic/chromium/quic_stream_factory.cc
@@ -36,15 +36,15 @@
 #include "net/quic/chromium/bidirectional_stream_quic_impl.h"
 #include "net/quic/chromium/crypto/channel_id_chromium.h"
 #include "net/quic/chromium/crypto/proof_verifier_chromium.h"
+#include "net/quic/chromium/properties_based_quic_server_info.h"
 #include "net/quic/chromium/quic_chromium_alarm_factory.h"
 #include "net/quic/chromium/quic_chromium_connection_helper.h"
 #include "net/quic/chromium/quic_chromium_packet_reader.h"
 #include "net/quic/chromium/quic_chromium_packet_writer.h"
 #include "net/quic/chromium/quic_crypto_client_stream_factory.h"
+#include "net/quic/chromium/quic_server_info.h"
 #include "net/quic/core/crypto/proof_verifier.h"
-#include "net/quic/core/crypto/properties_based_quic_server_info.h"
 #include "net/quic/core/crypto/quic_random.h"
-#include "net/quic/core/crypto/quic_server_info.h"
 #include "net/quic/core/quic_client_promised_info.h"
 #include "net/quic/core/quic_connection.h"
 #include "net/quic/core/quic_flags.h"
diff --git a/net/quic/chromium/quic_stream_factory_test.cc b/net/quic/chromium/quic_stream_factory_test.cc
index 14380c8..78031a89 100644
--- a/net/quic/chromium/quic_stream_factory_test.cc
+++ b/net/quic/chromium/quic_stream_factory_test.cc
@@ -28,14 +28,14 @@
 #include "net/quic/chromium/mock_crypto_client_stream_factory.h"
 #include "net/quic/chromium/mock_network_change_notifier.h"
 #include "net/quic/chromium/mock_quic_data.h"
+#include "net/quic/chromium/properties_based_quic_server_info.h"
 #include "net/quic/chromium/quic_http_utils.h"
+#include "net/quic/chromium/quic_server_info.h"
 #include "net/quic/chromium/quic_test_packet_maker.h"
 #include "net/quic/core/crypto/crypto_handshake.h"
-#include "net/quic/core/crypto/properties_based_quic_server_info.h"
 #include "net/quic/core/crypto/quic_crypto_client_config.h"
 #include "net/quic/core/crypto/quic_decrypter.h"
 #include "net/quic/core/crypto/quic_encrypter.h"
-#include "net/quic/core/crypto/quic_server_info.h"
 #include "net/quic/core/quic_client_promised_info.h"
 #include "net/quic/test_tools/mock_clock.h"
 #include "net/quic/test_tools/mock_random.h"
diff --git a/net/quic/core/crypto/aead_base_decrypter.cc b/net/quic/core/crypto/aead_base_decrypter.cc
index f6763217..060b955e 100644
--- a/net/quic/core/crypto/aead_base_decrypter.cc
+++ b/net/quic/core/crypto/aead_base_decrypter.cc
@@ -4,10 +4,9 @@
 
 #include "net/quic/core/crypto/aead_base_decrypter.h"
 
-#include <memory>
+#include <cstdint>
 
 #include "net/quic/core/quic_bug_tracker.h"
-#include "net/quic/core/quic_flags.h"
 #include "net/quic/core/quic_utils.h"
 #include "third_party/boringssl/src/include/openssl/err.h"
 #include "third_party/boringssl/src/include/openssl/evp.h"
diff --git a/net/quic/core/crypto/aead_base_decrypter.h b/net/quic/core/crypto/aead_base_decrypter.h
index d2c7f35..58f8fb5 100644
--- a/net/quic/core/crypto/aead_base_decrypter.h
+++ b/net/quic/core/crypto/aead_base_decrypter.h
@@ -5,7 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_AEAD_BASE_DECRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_AEAD_BASE_DECRYPTER_H_
 
-#include <stddef.h>
+#include <cstddef>
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
diff --git a/net/quic/core/crypto/aead_base_encrypter.cc b/net/quic/core/crypto/aead_base_encrypter.cc
index 9917ae05..b6916b9 100644
--- a/net/quic/core/crypto/aead_base_encrypter.cc
+++ b/net/quic/core/crypto/aead_base_encrypter.cc
@@ -2,12 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <string.h>
-
-#include <memory>
-
 #include "net/quic/core/crypto/aead_base_encrypter.h"
-#include "net/quic/core/quic_flags.h"
+
+#include <string>
+
 #include "net/quic/core/quic_utils.h"
 #include "third_party/boringssl/src/include/openssl/err.h"
 #include "third_party/boringssl/src/include/openssl/evp.h"
diff --git a/net/quic/core/crypto/aead_base_encrypter.h b/net/quic/core/crypto/aead_base_encrypter.h
index 1c2a1cc..76362766 100644
--- a/net/quic/core/crypto/aead_base_encrypter.h
+++ b/net/quic/core/crypto/aead_base_encrypter.h
@@ -5,7 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_AEAD_BASE_ENCRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_AEAD_BASE_ENCRYPTER_H_
 
-#include <stddef.h>
+#include <cstddef>
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
diff --git a/net/quic/core/crypto/aes_128_gcm_12_decrypter.h b/net/quic/core/crypto/aes_128_gcm_12_decrypter.h
index 1c3b6302..f568c2ef 100644
--- a/net/quic/core/crypto/aes_128_gcm_12_decrypter.h
+++ b/net/quic/core/crypto/aes_128_gcm_12_decrypter.h
@@ -5,8 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_AES_128_GCM_12_DECRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_AES_128_GCM_12_DECRYPTER_H_
 
-#include <stddef.h>
-#include <stdint.h>
+#include <cstdint>
 
 #include "base/macros.h"
 #include "net/quic/core/crypto/aead_base_decrypter.h"
diff --git a/net/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc b/net/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc
index 3ca950a..4539024 100644
--- a/net/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc
+++ b/net/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 
-#include "net/quic/core/quic_flags.h"
 #include "net/quic/core/quic_utils.h"
 #include "net/quic/test_tools/quic_test_utils.h"
 
diff --git a/net/quic/core/crypto/aes_128_gcm_12_encrypter.h b/net/quic/core/crypto/aes_128_gcm_12_encrypter.h
index 7b45da97..4ee5e47ee 100644
--- a/net/quic/core/crypto/aes_128_gcm_12_encrypter.h
+++ b/net/quic/core/crypto/aes_128_gcm_12_encrypter.h
@@ -5,8 +5,6 @@
 #ifndef NET_QUIC_CORE_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_
 
-#include <stddef.h>
-
 #include "base/macros.h"
 #include "net/quic/core/crypto/aead_base_encrypter.h"
 #include "net/quic/platform/api/quic_export.h"
diff --git a/net/quic/core/crypto/chacha20_poly1305_decrypter.h b/net/quic/core/crypto/chacha20_poly1305_decrypter.h
index 6b33f87..d671d68 100644
--- a/net/quic/core/crypto/chacha20_poly1305_decrypter.h
+++ b/net/quic/core/crypto/chacha20_poly1305_decrypter.h
@@ -5,8 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_DECRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_DECRYPTER_H_
 
-#include <stddef.h>
-#include <stdint.h>
+#include <cstdint>
 
 #include "base/macros.h"
 #include "net/quic/core/crypto/aead_base_decrypter.h"
diff --git a/net/quic/core/crypto/chacha20_poly1305_decrypter_test.cc b/net/quic/core/crypto/chacha20_poly1305_decrypter_test.cc
index 52762b8..94604cb 100644
--- a/net/quic/core/crypto/chacha20_poly1305_decrypter_test.cc
+++ b/net/quic/core/crypto/chacha20_poly1305_decrypter_test.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 
-#include "net/quic/core/quic_flags.h"
 #include "net/quic/core/quic_utils.h"
 #include "net/quic/test_tools/quic_test_utils.h"
 
diff --git a/net/quic/core/crypto/chacha20_poly1305_encrypter.h b/net/quic/core/crypto/chacha20_poly1305_encrypter.h
index a537d1b..dd4d4a6 100644
--- a/net/quic/core/crypto/chacha20_poly1305_encrypter.h
+++ b/net/quic/core/crypto/chacha20_poly1305_encrypter.h
@@ -5,8 +5,6 @@
 #ifndef NET_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_ENCRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_ENCRYPTER_H_
 
-#include <stddef.h>
-
 #include "base/macros.h"
 #include "net/quic/core/crypto/aead_base_encrypter.h"
 #include "net/quic/platform/api/quic_export.h"
@@ -14,10 +12,9 @@
 namespace net {
 
 // A ChaCha20Poly1305Encrypter is a QuicEncrypter that implements the
-// AEAD_CHACHA20_POLY1305 algorithm specified in
-// draft-agl-tls-chacha20poly1305-04, except that it truncates the Poly1305
-// authenticator to 12 bytes. Create an instance by calling
-// QuicEncrypter::Create(kCC12).
+// AEAD_CHACHA20_POLY1305 algorithm specified in RFC 7539, except that
+// it truncates the Poly1305 authenticator to 12 bytes. Create an instance
+// by calling QuicEncrypter::Create(kCC12).
 //
 // It uses an authentication tag of 16 bytes (128 bits). There is no
 // fixed nonce prefix.
diff --git a/net/quic/core/crypto/channel_id.cc b/net/quic/core/crypto/channel_id.cc
index abc0ae5b..6370180 100644
--- a/net/quic/core/crypto/channel_id.cc
+++ b/net/quic/core/crypto/channel_id.cc
@@ -4,10 +4,10 @@
 
 #include "net/quic/core/crypto/channel_id.h"
 
-#include "crypto/openssl_util.h"
+#include <cstdint>
+
 #include "third_party/boringssl/src/include/openssl/bn.h"
 #include "third_party/boringssl/src/include/openssl/ec.h"
-#include "third_party/boringssl/src/include/openssl/ec_key.h"
 #include "third_party/boringssl/src/include/openssl/ecdsa.h"
 #include "third_party/boringssl/src/include/openssl/nid.h"
 #include "third_party/boringssl/src/include/openssl/sha.h"
@@ -39,7 +39,7 @@
 
   bssl::UniquePtr<EC_GROUP> p256(
       EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
-  if (!p256) {
+  if (p256.get() == nullptr) {
     return false;
   }
 
diff --git a/net/quic/core/crypto/channel_id_test.cc b/net/quic/core/crypto/channel_id_test.cc
index 2b364d46..0309e65c 100644
--- a/net/quic/core/crypto/channel_id_test.cc
+++ b/net/quic/core/crypto/channel_id_test.cc
@@ -7,7 +7,6 @@
 #include <memory>
 
 #include "net/quic/test_tools/crypto_test_utils.h"
-#include "net/quic/test_tools/quic_test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::StringPiece;
@@ -202,36 +201,83 @@
     },
     {nullptr}};
 
+// Returns true if |ch| is a lowercase hexadecimal digit.
+bool IsHexDigit(char ch) {
+  return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f');
+}
+
+// Converts a lowercase hexadecimal digit to its integer value.
+int HexDigitToInt(char ch) {
+  if ('0' <= ch && ch <= '9') {
+    return ch - '0';
+  }
+  return ch - 'a' + 10;
+}
+
+// |in| is a string consisting of lowercase hexadecimal digits, where
+// every two digits represent one byte. |out| is a buffer of size |max_len|.
+// Converts |in| to bytes and stores the bytes in the |out| buffer. The
+// number of bytes converted is returned in |*out_len|. Returns true on
+// success, false on failure.
+bool DecodeHexString(const char* in,
+                     char* out,
+                     size_t* out_len,
+                     size_t max_len) {
+  if (!in) {
+    *out_len = static_cast<size_t>(-1);
+    return true;
+  }
+  *out_len = 0;
+  while (*in != '\0') {
+    if (!IsHexDigit(*in) || !IsHexDigit(*(in + 1))) {
+      return false;
+    }
+    if (*out_len >= max_len) {
+      return false;
+    }
+    out[*out_len] = HexDigitToInt(*in) * 16 + HexDigitToInt(*(in + 1));
+    (*out_len)++;
+    in += 2;
+  }
+  return true;
+}
+
 }  // namespace
 
 // A known answer test for ChannelIDVerifier.
 TEST(ChannelIDTest, VerifyKnownAnswerTest) {
-  string msg;
-  string qx;
-  string qy;
-  string r;
-  string s;
+  char msg[1024];
+  size_t msg_len;
+  char key[64];
+  size_t qx_len;
+  size_t qy_len;
+  char signature[64];
+  size_t r_len;
+  size_t s_len;
 
   for (size_t i = 0; test_vector[i].msg != nullptr; i++) {
     SCOPED_TRACE(i);
     // Decode the test vector.
-    ASSERT_TRUE(DecodeHexString(test_vector[i].msg, &msg));
-    ASSERT_TRUE(DecodeHexString(test_vector[i].qx, &qx));
-    ASSERT_TRUE(DecodeHexString(test_vector[i].qy, &qy));
-    ASSERT_TRUE(DecodeHexString(test_vector[i].r, &r));
-    ASSERT_TRUE(DecodeHexString(test_vector[i].s, &s));
-
-    string key = qx + qy;
-    string signature = r + s;
+    ASSERT_TRUE(
+        DecodeHexString(test_vector[i].msg, msg, &msg_len, sizeof(msg)));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].qx, key, &qx_len, sizeof(key)));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].qy, key + qx_len, &qy_len,
+                                sizeof(key) - qx_len));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].r, signature, &r_len,
+                                sizeof(signature)));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].s, signature + r_len, &s_len,
+                                sizeof(signature) - r_len));
 
     // The test vector's lengths should look sane.
-    EXPECT_EQ(32u, qx.size());
-    EXPECT_EQ(32u, qy.size());
-    EXPECT_EQ(32u, r.size());
-    EXPECT_EQ(32u, s.size());
+    EXPECT_EQ(sizeof(key) / 2, qx_len);
+    EXPECT_EQ(sizeof(key) / 2, qy_len);
+    EXPECT_EQ(sizeof(signature) / 2, r_len);
+    EXPECT_EQ(sizeof(signature) / 2, s_len);
 
     EXPECT_EQ(test_vector[i].result,
-              ChannelIDVerifier::VerifyRaw(key, msg, signature, false));
+              ChannelIDVerifier::VerifyRaw(
+                  StringPiece(key, sizeof(key)), StringPiece(msg, msg_len),
+                  StringPiece(signature, sizeof(signature)), false));
   }
 }
 
diff --git a/net/quic/core/crypto/common_cert_set_test.cc b/net/quic/core/crypto/common_cert_set_test.cc
index 3eea6ee..b355da21 100644
--- a/net/quic/core/crypto/common_cert_set_test.cc
+++ b/net/quic/core/crypto/common_cert_set_test.cc
@@ -4,7 +4,7 @@
 
 #include "net/quic/core/crypto/common_cert_set.h"
 
-#include <stdint.h>
+#include <cstdint>
 
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/net/quic/core/crypto/crypto_framer.cc b/net/quic/core/crypto/crypto_framer.cc
index 2fd7239..c24c72db 100644
--- a/net/quic/core/crypto/crypto_framer.cc
+++ b/net/quic/core/crypto/crypto_framer.cc
@@ -17,7 +17,7 @@
 
 namespace {
 
-const size_t kQuicTagSize = sizeof(uint32_t);
+const size_t kQuicTagSize = sizeof(QuicTag);
 const size_t kCryptoEndOffsetSize = sizeof(uint32_t);
 const size_t kNumEntriesSize = sizeof(uint16_t);
 
diff --git a/net/quic/core/crypto/crypto_framer.h b/net/quic/core/crypto/crypto_framer.h
index bd316fd..2ed2f32 100644
--- a/net/quic/core/crypto/crypto_framer.h
+++ b/net/quic/core/crypto/crypto_framer.h
@@ -7,7 +7,6 @@
 
 #include <cstddef>
 #include <cstdint>
-#include <memory>
 #include <utility>
 #include <vector>
 
diff --git a/net/quic/core/crypto/crypto_handshake.h b/net/quic/core/crypto/crypto_handshake.h
index c985e5bb..b646c58 100644
--- a/net/quic/core/crypto/crypto_handshake.h
+++ b/net/quic/core/crypto/crypto_handshake.h
@@ -5,8 +5,6 @@
 #ifndef NET_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_H_
 #define NET_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_H_
 
-#include <stdint.h>
-
 #include <memory>
 #include <string>
 #include <vector>
diff --git a/net/quic/core/crypto/crypto_handshake_message.cc b/net/quic/core/crypto/crypto_handshake_message.cc
index 2f9e230..74c978f 100644
--- a/net/quic/core/crypto/crypto_handshake_message.cc
+++ b/net/quic/core/crypto/crypto_handshake_message.cc
@@ -15,6 +15,7 @@
 #include "net/quic/core/quic_socket_address_coder.h"
 #include "net/quic/core/quic_utils.h"
 
+using base::ContainsKey;
 using base::StringPiece;
 using base::StringPrintf;
 using std::string;
@@ -111,7 +112,7 @@
 }
 
 bool CryptoHandshakeMessage::HasStringPiece(QuicTag tag) const {
-  return base::ContainsKey(tag_value_map_, tag);
+  return ContainsKey(tag_value_map_, tag);
 }
 
 QuicErrorCode CryptoHandshakeMessage::GetNthValue24(QuicTag tag,
diff --git a/net/quic/core/crypto/crypto_handshake_message.h b/net/quic/core/crypto/crypto_handshake_message.h
index 22f8205..f1e7b99 100644
--- a/net/quic/core/crypto/crypto_handshake_message.h
+++ b/net/quic/core/crypto/crypto_handshake_message.h
@@ -5,9 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_MESSAGE_H_
 #define NET_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_MESSAGE_H_
 
-#include <stddef.h>
-#include <stdint.h>
-
+#include <cstddef>
 #include <cstdint>
 #include <memory>
 #include <string>
diff --git a/net/quic/core/crypto/crypto_handshake_message_test.cc b/net/quic/core/crypto/crypto_handshake_message_test.cc
index 7032a23..088cd37e 100644
--- a/net/quic/core/crypto/crypto_handshake_message_test.cc
+++ b/net/quic/core/crypto/crypto_handshake_message_test.cc
@@ -6,7 +6,7 @@
 
 #include "net/quic/core/crypto/crypto_handshake.h"
 #include "net/quic/core/crypto/crypto_protocol.h"
-#include "net/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace net {
 namespace test {
diff --git a/net/quic/core/crypto/crypto_protocol.h b/net/quic/core/crypto/crypto_protocol.h
index 3c6d9c3..8ea145e 100644
--- a/net/quic/core/crypto/crypto_protocol.h
+++ b/net/quic/core/crypto/crypto_protocol.h
@@ -5,9 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_CRYPTO_PROTOCOL_H_
 #define NET_QUIC_CORE_CRYPTO_CRYPTO_PROTOCOL_H_
 
-#include <stddef.h>
-#include <stdint.h>
-
+#include <cstddef>
 #include <string>
 
 #include "net/quic/core/quic_tag.h"
diff --git a/net/quic/core/crypto/crypto_secret_boxer.h b/net/quic/core/crypto/crypto_secret_boxer.h
index 14737e0..93e6f90f 100644
--- a/net/quic/core/crypto/crypto_secret_boxer.h
+++ b/net/quic/core/crypto/crypto_secret_boxer.h
@@ -5,8 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_CRYPTO_SECRET_BOXER_H_
 #define NET_QUIC_CORE_CRYPTO_CRYPTO_SECRET_BOXER_H_
 
-#include <stddef.h>
-
+#include <cstddef>
 #include <string>
 #include <vector>
 
@@ -30,6 +29,7 @@
   // GetKeySize returns the number of bytes in a key.
   static size_t GetKeySize();
 
+  // SetKeys sets a list of encryption keys. The first key in the list will be
   // used by |Box|, but all supplied keys will be tried by |Unbox|, to handle
   // key skew across the fleet. This must be called before |Box| or |Unbox|.
   // Keys must be |GetKeySize()| bytes long.
@@ -38,7 +38,7 @@
   // Box encrypts |plaintext| using a random nonce generated from |rand| and
   // returns the resulting ciphertext. Since an authenticator and nonce are
   // included, the result will be slightly larger than |plaintext|. The first
-  // key in the std::vector supplied to |SetKeys| will be used.
+  // key in the vector supplied to |SetKeys| will be used.
   std::string Box(QuicRandom* rand, base::StringPiece plaintext) const;
 
   // Unbox takes the result of a previous call to |Box| in |ciphertext| and
diff --git a/net/quic/core/crypto/crypto_secret_boxer_test.cc b/net/quic/core/crypto/crypto_secret_boxer_test.cc
index c7712f4..1e8c1d1 100644
--- a/net/quic/core/crypto/crypto_secret_boxer_test.cc
+++ b/net/quic/core/crypto/crypto_secret_boxer_test.cc
@@ -4,8 +4,6 @@
 
 #include "net/quic/core/crypto/crypto_secret_boxer.h"
 
-#include <memory>
-
 #include "net/quic/core/crypto/quic_random.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/net/quic/core/crypto/crypto_server_config_protobuf.h b/net/quic/core/crypto/crypto_server_config_protobuf.h
index 8efff03..82dafd8 100644
--- a/net/quic/core/crypto/crypto_server_config_protobuf.h
+++ b/net/quic/core/crypto/crypto_server_config_protobuf.h
@@ -5,8 +5,8 @@
 #ifndef NET_QUIC_CORE_CRYPTO_CRYPTO_SERVER_CONFIG_PROTOBUF_H_
 #define NET_QUIC_CORE_CRYPTO_CRYPTO_SERVER_CONFIG_PROTOBUF_H_
 
-#include <stddef.h>
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
 
 #include <memory>
 #include <string>
diff --git a/net/quic/core/crypto/crypto_server_test.cc b/net/quic/core/crypto/crypto_server_test.cc
index fdf91b7..5d0e30c 100644
--- a/net/quic/core/crypto/crypto_server_test.cc
+++ b/net/quic/core/crypto/crypto_server_test.cc
@@ -8,7 +8,6 @@
 #include <ostream>
 #include <vector>
 
-#include "base/strings/string_number_conversions.h"
 #include "crypto/secure_hash.h"
 #include "net/quic/core/crypto/cert_compressor.h"
 #include "net/quic/core/crypto/common_cert_set.h"
@@ -45,7 +44,7 @@
   void Run(bool ok,
            const std::string& error_details,
            std::unique_ptr<ProofVerifyDetails>* details) override {
-    // Do nothing
+    DCHECK(false);
   }
 };
 
@@ -862,6 +861,7 @@
       "#004b5453", srct_hex_.c_str(),
       "PUBS", pub_hex_.c_str(),
       "NONC", nonce_hex_.c_str(),
+      "NONP", "123456789012345678901234567890",
       "VER\0", client_version_string_.c_str(),
       "XLCT", XlctHexString().c_str(),
       "$padding", static_cast<int>(kClientHelloMinimumSize),
@@ -952,7 +952,6 @@
       "#004b5453", srct_hex_.c_str(),
       "PUBS", pub_hex_.c_str(),
       "NONC", nonce_hex_.c_str(),
-      "NONP", "123456789012345678901234567890",
       "VER\0", client_version_string_.c_str(),
       "XLCT", XlctHexString().c_str(),
       "$padding", static_cast<int>(kClientHelloMinimumSize),
diff --git a/net/quic/core/crypto/crypto_utils.h b/net/quic/core/crypto/crypto_utils.h
index 044c8b7..ee0825f 100644
--- a/net/quic/core/crypto/crypto_utils.h
+++ b/net/quic/core/crypto/crypto_utils.h
@@ -7,9 +7,8 @@
 #ifndef NET_QUIC_CORE_CRYPTO_CRYPTO_UTILS_H_
 #define NET_QUIC_CORE_CRYPTO_CRYPTO_UTILS_H_
 
-#include <stddef.h>
-#include <stdint.h>
-
+#include <cstddef>
+#include <cstdint>
 #include <string>
 
 #include "base/macros.h"
diff --git a/net/quic/core/crypto/crypto_utils_test.cc b/net/quic/core/crypto/crypto_utils_test.cc
index 34035233..1b34c50 100644
--- a/net/quic/core/crypto/crypto_utils_test.cc
+++ b/net/quic/core/crypto/crypto_utils_test.cc
@@ -4,6 +4,7 @@
 
 #include "net/quic/core/crypto/crypto_utils.h"
 
+#include "net/quic/core/quic_utils.h"
 #include "net/quic/test_tools/quic_test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -18,9 +19,8 @@
   EXPECT_FALSE(CryptoUtils::IsValidSNI("192.168.0.1"));
   // SNI without any dot.
   EXPECT_FALSE(CryptoUtils::IsValidSNI("somedomain"));
-  // Invalid RFC2396 hostname
-  // TODO(rtenneti): Support RFC2396 hostname.
-  // EXPECT_FALSE(CryptoUtils::IsValidSNI("some_domain.com"));
+  // Invalid by RFC2396 but unfortunately domains of this form exist.
+  EXPECT_TRUE(CryptoUtils::IsValidSNI("some_domain.com"));
   // An empty string must be invalid otherwise the QUIC client will try sending
   // it.
   EXPECT_FALSE(CryptoUtils::IsValidSNI(""));
diff --git a/net/quic/core/crypto/curve25519_key_exchange_test.cc b/net/quic/core/crypto/curve25519_key_exchange_test.cc
index dcd3352..8dad088 100644
--- a/net/quic/core/crypto/curve25519_key_exchange_test.cc
+++ b/net/quic/core/crypto/curve25519_key_exchange_test.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 
-#include "base/strings/string_piece.h"
 #include "net/quic/core/crypto/quic_random.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/net/quic/core/crypto/local_strike_register_client.h b/net/quic/core/crypto/local_strike_register_client.h
index 7c00fce..2679bc2f 100644
--- a/net/quic/core/crypto/local_strike_register_client.h
+++ b/net/quic/core/crypto/local_strike_register_client.h
@@ -5,7 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_LOCAL_STRIKE_REGISTER_CLIENT_H_
 #define NET_QUIC_CORE_CRYPTO_LOCAL_STRIKE_REGISTER_CLIENT_H_
 
-#include <stdint.h>
+#include <cstdint>
 
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
diff --git a/net/quic/core/crypto/null_decrypter.cc b/net/quic/core/crypto/null_decrypter.cc
index b33d535..b6e7e538 100644
--- a/net/quic/core/crypto/null_decrypter.cc
+++ b/net/quic/core/crypto/null_decrypter.cc
@@ -4,7 +4,7 @@
 
 #include "net/quic/core/crypto/null_decrypter.h"
 
-#include <stdint.h>
+#include <cstdint>
 
 #include "net/quic/core/quic_bug_tracker.h"
 #include "net/quic/core/quic_data_reader.h"
diff --git a/net/quic/core/crypto/null_decrypter.h b/net/quic/core/crypto/null_decrypter.h
index 28b6328..57dbe5b 100644
--- a/net/quic/core/crypto/null_decrypter.h
+++ b/net/quic/core/crypto/null_decrypter.h
@@ -5,8 +5,8 @@
 #ifndef NET_QUIC_CORE_CRYPTO_NULL_DECRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_NULL_DECRYPTER_H_
 
-#include <stddef.h>
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
diff --git a/net/quic/core/crypto/null_encrypter.h b/net/quic/core/crypto/null_encrypter.h
index 988eedc..96b1bb7 100644
--- a/net/quic/core/crypto/null_encrypter.h
+++ b/net/quic/core/crypto/null_encrypter.h
@@ -5,7 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_NULL_ENCRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_NULL_ENCRYPTER_H_
 
-#include <stddef.h>
+#include <cstddef>
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
diff --git a/net/quic/core/crypto/p256_key_exchange.cc b/net/quic/core/crypto/p256_key_exchange.cc
index bfef924..62a5890 100644
--- a/net/quic/core/crypto/p256_key_exchange.cc
+++ b/net/quic/core/crypto/p256_key_exchange.cc
@@ -4,11 +4,15 @@
 
 #include "net/quic/core/crypto/p256_key_exchange.h"
 
+#include <cstdint>
+#include <memory>
+#include <string>
 #include <utility>
 
 #include "base/logging.h"
 #include "third_party/boringssl/src/include/openssl/ec.h"
 #include "third_party/boringssl/src/include/openssl/ecdh.h"
+#include "third_party/boringssl/src/include/openssl/err.h"
 #include "third_party/boringssl/src/include/openssl/evp.h"
 
 using base::StringPiece;
@@ -88,7 +92,7 @@
 
   bssl::UniquePtr<EC_POINT> point(
       EC_POINT_new(EC_KEY_get0_group(private_key_.get())));
-  if (!point ||
+  if (!point.get() ||
       !EC_POINT_oct2point(/* also test if point is on curve */
                           EC_KEY_get0_group(private_key_.get()), point.get(),
                           reinterpret_cast<const uint8_t*>(
diff --git a/net/quic/core/crypto/p256_key_exchange.h b/net/quic/core/crypto/p256_key_exchange.h
index 575dec4..60c64fa 100644
--- a/net/quic/core/crypto/p256_key_exchange.h
+++ b/net/quic/core/crypto/p256_key_exchange.h
@@ -5,14 +5,11 @@
 #ifndef NET_QUIC_CORE_CRYPTO_P256_KEY_EXCHANGE_H_
 #define NET_QUIC_CORE_CRYPTO_P256_KEY_EXCHANGE_H_
 
-#include <stdint.h>
-
-#include <memory>
+#include <cstdint>
 #include <string>
 
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
-#include "crypto/openssl_util.h"
 #include "net/quic/core/crypto/key_exchange.h"
 #include "net/quic/platform/api/quic_export.h"
 #include "third_party/boringssl/src/include/openssl/base.h"
@@ -66,4 +63,5 @@
 };
 
 }  // namespace net
+
 #endif  // NET_QUIC_CORE_CRYPTO_P256_KEY_EXCHANGE_H_
diff --git a/net/quic/core/crypto/p256_key_exchange_test.cc b/net/quic/core/crypto/p256_key_exchange_test.cc
index 7f681228..564382c 100644
--- a/net/quic/core/crypto/p256_key_exchange_test.cc
+++ b/net/quic/core/crypto/p256_key_exchange_test.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 
-#include "base/logging.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using std::string;
diff --git a/net/quic/core/crypto/proof_source.cc b/net/quic/core/crypto/proof_source.cc
index 9a72345..7b7f38b 100644
--- a/net/quic/core/crypto/proof_source.cc
+++ b/net/quic/core/crypto/proof_source.cc
@@ -4,10 +4,11 @@
 
 #include "net/quic/core/crypto/proof_source.h"
 
+using std::string;
+
 namespace net {
 
-ProofSource::Chain::Chain(const std::vector<std::string>& certs)
-    : certs(certs) {}
+ProofSource::Chain::Chain(const std::vector<string>& certs) : certs(certs) {}
 
 ProofSource::Chain::~Chain() {}
 
diff --git a/net/quic/core/crypto/proof_source.h b/net/quic/core/crypto/proof_source.h
index 8db52f7..8fd184f 100644
--- a/net/quic/core/crypto/proof_source.h
+++ b/net/quic/core/crypto/proof_source.h
@@ -21,7 +21,7 @@
 // chains and signatures that prove its identity.
 class QUIC_EXPORT_PRIVATE ProofSource {
  public:
-  // Chain is a reference-counted wrapper for a std::vector of std::stringified
+  // Chain is a reference-counted wrapper for a vector of stringified
   // certificates.
   struct QUIC_EXPORT_PRIVATE Chain : public QuicReferenceCounted {
     explicit Chain(const std::vector<std::string>& certs);
diff --git a/net/quic/core/crypto/quic_compressed_certs_cache.h b/net/quic/core/crypto/quic_compressed_certs_cache.h
index 7ae67645..cb1daa8e 100644
--- a/net/quic/core/crypto/quic_compressed_certs_cache.h
+++ b/net/quic/core/crypto/quic_compressed_certs_cache.h
@@ -74,7 +74,6 @@
     CachedCerts(const UncompressedCerts& uncompressed_certs,
                 const std::string& compressed_cert);
     CachedCerts(const CachedCerts& other);
-
     ~CachedCerts();
 
     // Returns true if the |uncompressed_certs| matches uncompressed
diff --git a/net/quic/core/crypto/quic_compressed_certs_cache_test.cc b/net/quic/core/crypto/quic_compressed_certs_cache_test.cc
index 456387e..0b61b95e 100644
--- a/net/quic/core/crypto/quic_compressed_certs_cache_test.cc
+++ b/net/quic/core/crypto/quic_compressed_certs_cache_test.cc
@@ -11,6 +11,7 @@
 #include "net/quic/test_tools/crypto_test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using base::IntToString;
 using std::string;
 
 namespace net {
@@ -58,6 +59,8 @@
                          chain, "mismatched common certs", cached_certs));
   EXPECT_EQ(nullptr, certs_cache_.GetCompressedCert(chain, common_certs,
                                                     "mismatched cached certs"));
+
+  // A different chain though with equivalent certs should get a cache miss.
   QuicReferenceCountedPointer<ProofSource::Chain> chain2(
       new ProofSource::Chain(certs));
   EXPECT_EQ(nullptr,
@@ -81,7 +84,7 @@
   for (unsigned int i = 0;
        i < QuicCompressedCertsCache::kQuicCompressedCertsCacheSize; i++) {
     EXPECT_EQ(certs_cache_.Size(), i + 1);
-    certs_cache_.Insert(chain, base::IntToString(i), "", base::IntToString(i));
+    certs_cache_.Insert(chain, IntToString(i), "", IntToString(i));
   }
   EXPECT_EQ(certs_cache_.MaxSize(), certs_cache_.Size());
 
diff --git a/net/quic/core/crypto/quic_crypto_client_config.h b/net/quic/core/crypto/quic_crypto_client_config.h
index c20ea1f..551307b 100644
--- a/net/quic/core/crypto/quic_crypto_client_config.h
+++ b/net/quic/core/crypto/quic_crypto_client_config.h
@@ -5,8 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_QUIC_CRYPTO_CLIENT_CONFIG_H_
 #define NET_QUIC_CORE_CRYPTO_QUIC_CRYPTO_CLIENT_CONFIG_H_
 
-#include <stdint.h>
-
+#include <cstdint>
 #include <map>
 #include <memory>
 #include <queue>
@@ -85,7 +84,7 @@
     // InvalidateServerConfig clears the cached server config (if any).
     void InvalidateServerConfig();
 
-    // SetProof stores a certificate chain and signature.
+    // SetProof stores a cert chain, cert signed timestamp and signature.
     void SetProof(const std::vector<std::string>& certs,
                   base::StringPiece cert_sct,
                   base::StringPiece chlo_hash,
@@ -202,6 +201,8 @@
   // Used to filter server ids for partial config deletion.
   class ServerIdFilter {
    public:
+    virtual ~ServerIdFilter() {}
+
     // Returns true if |server_id| matches the filter.
     virtual bool Matches(const QuicServerId& server_id) const = 0;
   };
diff --git a/net/quic/core/crypto/quic_crypto_server_config.cc b/net/quic/core/crypto/quic_crypto_server_config.cc
index 35de5ecc..1e30ff3 100644
--- a/net/quic/core/crypto/quic_crypto_server_config.cc
+++ b/net/quic/core/crypto/quic_crypto_server_config.cc
@@ -12,7 +12,6 @@
 #include "base/macros.h"
 #include "crypto/hkdf.h"
 #include "crypto/secure_hash.h"
-#include "net/base/ip_address.h"
 #include "net/quic/core/crypto/aes_128_gcm_12_decrypter.h"
 #include "net/quic/core/crypto/aes_128_gcm_12_encrypter.h"
 #include "net/quic/core/crypto/cert_compressor.h"
@@ -2025,8 +2024,7 @@
       priority(0),
       source_address_token_boxer(nullptr) {}
 
-QuicCryptoServerConfig::Config::~Config() {
-}
+QuicCryptoServerConfig::Config::~Config() {}
 
 QuicSignedServerConfig::QuicSignedServerConfig() {}
 QuicSignedServerConfig::~QuicSignedServerConfig() {}
diff --git a/net/quic/core/crypto/quic_crypto_server_config.h b/net/quic/core/crypto/quic_crypto_server_config.h
index a430b29..4a6026f 100644
--- a/net/quic/core/crypto/quic_crypto_server_config.h
+++ b/net/quic/core/crypto/quic_crypto_server_config.h
@@ -5,9 +5,8 @@
 #ifndef NET_QUIC_CORE_CRYPTO_QUIC_CRYPTO_SERVER_CONFIG_H_
 #define NET_QUIC_CORE_CRYPTO_QUIC_CRYPTO_SERVER_CONFIG_H_
 
-#include <stddef.h>
-#include <stdint.h>
-
+#include <cstddef>
+#include <cstdint>
 #include <map>
 #include <memory>
 #include <string>
@@ -15,8 +14,6 @@
 
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
-#include "net/base/ip_address.h"
-#include "net/base/ip_endpoint.h"
 #include "net/quic/core/crypto/crypto_handshake.h"
 #include "net/quic/core/crypto/crypto_handshake_message.h"
 #include "net/quic/core/crypto/crypto_protocol.h"
diff --git a/net/quic/core/crypto/quic_crypto_server_config_test.cc b/net/quic/core/crypto/quic_crypto_server_config_test.cc
index a3d98f2..da7f858 100644
--- a/net/quic/core/crypto/quic_crypto_server_config_test.cc
+++ b/net/quic/core/crypto/quic_crypto_server_config_test.cc
@@ -8,21 +8,17 @@
 
 #include <memory>
 
-#include "net/quic/core/crypto/aes_128_gcm_12_encrypter.h"
 #include "net/quic/core/crypto/cert_compressor.h"
 #include "net/quic/core/crypto/chacha20_poly1305_encrypter.h"
 #include "net/quic/core/crypto/crypto_handshake_message.h"
 #include "net/quic/core/crypto/crypto_secret_boxer.h"
 #include "net/quic/core/crypto/crypto_server_config_protobuf.h"
 #include "net/quic/core/crypto/quic_random.h"
-#include "net/quic/core/quic_flags.h"
 #include "net/quic/core/quic_time.h"
 #include "net/quic/platform/api/quic_socket_address.h"
 #include "net/quic/test_tools/crypto_test_utils.h"
 #include "net/quic/test_tools/mock_clock.h"
 #include "net/quic/test_tools/quic_crypto_server_config_peer.h"
-#include "net/quic/test_tools/quic_test_utils.h"
-#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::StringPiece;
@@ -210,7 +206,7 @@
 
 // Test basic behavior of source address tokens including being specific
 // to a single IP address and server config.
-TEST_F(SourceAddressTokenTest, NewSourceAddressToken) {
+TEST_F(SourceAddressTokenTest, SourceAddressToken) {
   // Primary config generates configs that validate successfully.
   const string token4 = NewSourceAddressToken(kPrimary, ip4_);
   const string token4d = NewSourceAddressToken(kPrimary, ip4_dual_);
@@ -228,7 +224,7 @@
   ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token6, ip6_));
 }
 
-TEST_F(SourceAddressTokenTest, NewSourceAddressTokenExpiration) {
+TEST_F(SourceAddressTokenTest, SourceAddressTokenExpiration) {
   const string token = NewSourceAddressToken(kPrimary, ip4_);
 
   // Validation fails if the token is from the future.
@@ -242,7 +238,7 @@
             ValidateSourceAddressTokens(kPrimary, token, ip4_));
 }
 
-TEST_F(SourceAddressTokenTest, NewSourceAddressTokenWithNetworkParams) {
+TEST_F(SourceAddressTokenTest, SourceAddressTokenWithNetworkParams) {
   // Make sure that if the source address token contains CachedNetworkParameters
   // that this gets written to ValidateSourceAddressToken output argument.
   CachedNetworkParameters cached_network_params_input;
diff --git a/net/quic/core/crypto/quic_decrypter.h b/net/quic/core/crypto/quic_decrypter.h
index 1c518cce..824cf92 100644
--- a/net/quic/core/crypto/quic_decrypter.h
+++ b/net/quic/core/crypto/quic_decrypter.h
@@ -5,8 +5,8 @@
 #ifndef NET_QUIC_CORE_CRYPTO_QUIC_DECRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_QUIC_DECRYPTER_H_
 
-#include <stddef.h>
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
 
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/platform/api/quic_export.h"
diff --git a/net/quic/core/crypto/quic_encrypter.h b/net/quic/core/crypto/quic_encrypter.h
index 2bc045e..67398672 100644
--- a/net/quic/core/crypto/quic_encrypter.h
+++ b/net/quic/core/crypto/quic_encrypter.h
@@ -5,7 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_QUIC_ENCRYPTER_H_
 #define NET_QUIC_CORE_CRYPTO_QUIC_ENCRYPTER_H_
 
-#include <stddef.h>
+#include <cstddef>
 
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/platform/api/quic_export.h"
diff --git a/net/quic/core/crypto/quic_random.h b/net/quic/core/crypto/quic_random.h
index 179d3b39..65a632e 100644
--- a/net/quic/core/crypto/quic_random.h
+++ b/net/quic/core/crypto/quic_random.h
@@ -5,8 +5,8 @@
 #ifndef NET_QUIC_CORE_CRYPTO_QUIC_RANDOM_H_
 #define NET_QUIC_CORE_CRYPTO_QUIC_RANDOM_H_
 
-#include <stddef.h>
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
 
 #include "net/quic/platform/api/quic_export.h"
 
diff --git a/net/quic/core/crypto/strike_register.h b/net/quic/core/crypto/strike_register.h
index 59c1f944..d12ebc2 100644
--- a/net/quic/core/crypto/strike_register.h
+++ b/net/quic/core/crypto/strike_register.h
@@ -5,8 +5,7 @@
 #ifndef NET_QUIC_CORE_CRYPTO_STRIKE_REGISTER_H_
 #define NET_QUIC_CORE_CRYPTO_STRIKE_REGISTER_H_
 
-#include <stdint.h>
-
+#include <cstdint>
 #include <memory>
 #include <set>
 #include <utility>
diff --git a/net/quic/test_tools/quic_test_utils.cc b/net/quic/test_tools/quic_test_utils.cc
index 4e3df7c4..1090dfb 100644
--- a/net/quic/test_tools/quic_test_utils.cc
+++ b/net/quic/test_tools/quic_test_utils.cc
@@ -727,18 +727,6 @@
                 << HexDumpWithMarks(actual, actual_len, marks.get(), max_len);
 }
 
-bool DecodeHexString(const base::StringPiece& hex, std::string* bytes) {
-  bytes->clear();
-  if (hex.empty())
-    return true;
-  std::vector<uint8_t> v;
-  if (!base::HexStringToBytes(hex.as_string(), &v))
-    return false;
-  if (!v.empty())
-    bytes->assign(reinterpret_cast<const char*>(&v[0]), v.size());
-  return true;
-}
-
 static QuicPacket* ConstructPacketFromHandshakeMessage(
     QuicConnectionId connection_id,
     const CryptoHandshakeMessage& message,
diff --git a/net/quic/test_tools/quic_test_utils.h b/net/quic/test_tools/quic_test_utils.h
index 30ebef8..7b4a969e1 100644
--- a/net/quic/test_tools/quic_test_utils.h
+++ b/net/quic/test_tools/quic_test_utils.h
@@ -167,8 +167,6 @@
                                    const char* expected,
                                    const int expected_len);
 
-bool DecodeHexString(const StringPiece& hex, std::string* bytes);
-
 // Returns the length of a QuicPacket that is capable of holding either a
 // stream frame or a minimal ack frame.  Sets |*payload_length| to the number
 // of bytes of stream data that will fit in such a packet.
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index e359380..67151f6 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -687,7 +687,9 @@
     //
     // * Include both "strict" and "lax" same-site cookies if the request's
     //   |url|, |initiator|, and |first_party_for_cookies| all have the same
-    //   registrable domain.
+    //   registrable domain. Note: this also covers the case of a request
+    //   without an initiator (only happens for browser-initiated main frame
+    //   navigations).
     //
     // * Include only "lax" same-site cookies if the request's |URL| and
     //   |first_party_for_cookies| have the same registrable domain, _and_ the
@@ -700,7 +702,7 @@
     if (registry_controlled_domains::SameDomainOrHost(
             request_->url(), request_->first_party_for_cookies(),
             registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
-      if (request_->initiator() &&
+      if (!request_->initiator() ||
           registry_controlled_domains::SameDomainOrHost(
               request_->url(), request_->initiator().value().GetURL(),
               registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 60920ef3..5c3923d 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -2762,6 +2762,23 @@
     EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
   }
 
+  // Verify that both cookies are sent when the request has no initiator (can
+  // happen for main frame browser-initiated navigations).
+  {
+    TestDelegate d;
+    std::unique_ptr<URLRequest> req(default_context_.CreateRequest(
+        test_server.GetURL(kHost, "/echoheader?Cookie"), DEFAULT_PRIORITY, &d));
+    req->set_first_party_for_cookies(test_server.GetURL(kHost, "/"));
+    req->Start();
+    base::RunLoop().Run();
+
+    EXPECT_NE(std::string::npos,
+              d.data_received().find("StrictSameSiteCookie=1"));
+    EXPECT_NE(std::string::npos, d.data_received().find("LaxSameSiteCookie=1"));
+    EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+    EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+  }
+
   // Verify that both cookies are sent for same-registrable-domain requests.
   {
     TestDelegate d;
diff --git a/services/preferences/public/cpp/pref_observer_store.cc b/services/preferences/public/cpp/pref_observer_store.cc
index fac3833..569dcb0 100644
--- a/services/preferences/public/cpp/pref_observer_store.cc
+++ b/services/preferences/public/cpp/pref_observer_store.cc
@@ -8,20 +8,22 @@
 #include "mojo/public/cpp/bindings/array.h"
 #include "services/service_manager/public/cpp/connector.h"
 
+namespace preferences {
+
 PrefObserverStore::PrefObserverStore(
     prefs::mojom::PreferencesManagerPtr prefs_manager_ptr)
     : prefs_binding_(this),
       prefs_manager_ptr_(std::move(prefs_manager_ptr)),
       initialized_(false) {}
 
-void PrefObserverStore::Init(const std::set<std::string>& keys) {
-  DCHECK(!initialized_);
-  keys_ = keys;
+void PrefObserverStore::Subscribe(const std::set<std::string>& keys) {
+  if (keys_.empty())
+    prefs_manager_ptr_->AddObserver(prefs_binding_.CreateInterfacePtrAndBind());
+  keys_.insert(keys.begin(), keys.end());
 
   std::vector<std::string> pref_array;
   std::copy(keys_.begin(), keys_.end(), std::back_inserter(pref_array));
-  prefs_manager_ptr_->AddObserver(pref_array,
-                                  prefs_binding_.CreateInterfacePtrAndBind());
+  prefs_manager_ptr_->Subscribe(pref_array);
 }
 
 bool PrefObserverStore::GetValue(const std::string& key,
@@ -85,16 +87,17 @@
 
 void PrefObserverStore::OnPreferencesChanged(
     std::unique_ptr<base::DictionaryValue> preferences) {
-  for (base::DictionaryValue::Iterator it(*preferences); !it.IsAtEnd();
-       it.Advance()) {
-    if (keys_.find(it.key()) == keys_.end())
-      continue;
-    // We deliberately call the parent to avoid notifying the server again.
-    ValueMapPrefStore::SetValue(it.key(), it.value().CreateDeepCopy(), 0);
-  }
-
   if (!initialized_) {
     initialized_ = true;
     NotifyInitializationCompleted();
   }
+
+  for (base::DictionaryValue::Iterator it(*preferences); !it.IsAtEnd();
+       it.Advance()) {
+    if (keys_.find(it.key()) == keys_.end())
+      continue;
+    ValueMapPrefStore::SetValue(it.key(), it.value().CreateDeepCopy(), 0);
+  }
 }
+
+}  // namespace preferences
diff --git a/services/preferences/public/cpp/pref_observer_store.h b/services/preferences/public/cpp/pref_observer_store.h
index ea47460..0b723463 100644
--- a/services/preferences/public/cpp/pref_observer_store.h
+++ b/services/preferences/public/cpp/pref_observer_store.h
@@ -12,6 +12,8 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/preferences/public/interfaces/preferences.mojom.h"
 
+namespace preferences {
+
 class PrefObserverStoreTest;
 
 // An implementation of PrefStore which uses prefs::mojom::PreferenceManager as
@@ -30,9 +32,9 @@
   explicit PrefObserverStore(
       prefs::mojom::PreferencesManagerPtr prefs_manager_ptr);
 
-  // Defines the set of |keys| which PrefOvserverStore will handle. Begins
-  // listening for changes to these from |prefs_manager_|.
-  void Init(const std::set<std::string>& keys);
+  // Adds a set of |keys| which PrefObserverStore will handle. Begins listening
+  // for changes to these from |prefs_manager_|.
+  void Subscribe(const std::set<std::string>& keys);
 
   // PrefStore:
   bool GetValue(const std::string& key,
@@ -75,4 +77,5 @@
   DISALLOW_COPY_AND_ASSIGN(PrefObserverStore);
 };
 
+}  // namespace preferences
 #endif  // SERVICES_PREFERENCES_PUBLIC_CPP_PREFS_OBSERVER_STORE_H_
diff --git a/services/preferences/public/cpp/tests/pref_observer_store_unittest.cc b/services/preferences/public/cpp/tests/pref_observer_store_unittest.cc
index 26f948ab..9dbd0a5 100644
--- a/services/preferences/public/cpp/tests/pref_observer_store_unittest.cc
+++ b/services/preferences/public/cpp/tests/pref_observer_store_unittest.cc
@@ -33,10 +33,10 @@
   bool set_preferences_called() { return set_preferences_called_; }
 
   // prefs::mojom::TestPreferenceManager:
-  void AddObserver(const std::vector<std::string>& preferences,
-                   prefs::mojom::PreferencesObserverPtr client) override;
+  void AddObserver(prefs::mojom::PreferencesObserverPtr client) override;
   void SetPreferences(
       std::unique_ptr<base::DictionaryValue> preferences) override;
+  void Subscribe(const std::vector<std::string>& preferences) override;
 
  private:
   bool add_observer_called_;
@@ -48,11 +48,8 @@
 };
 
 void TestPreferenceManager::AddObserver(
-    const std::vector<std::string>& preferences,
     prefs::mojom::PreferencesObserverPtr client) {
   add_observer_called_ = true;
-  last_preference_set_.clear();
-  last_preference_set_.insert(preferences.begin(), preferences.end());
 }
 
 void TestPreferenceManager::SetPreferences(
@@ -60,8 +57,16 @@
   set_preferences_called_ = true;
 }
 
+void TestPreferenceManager::Subscribe(
+    const std::vector<std::string>& preferences) {
+  last_preference_set_.clear();
+  last_preference_set_.insert(preferences.begin(), preferences.end());
+}
+
 }  // namespace
 
+namespace preferences {
+
 class PrefObserverStoreTest : public testing::Test {
  public:
   PrefObserverStoreTest() {}
@@ -107,7 +112,7 @@
   std::set<std::string> keys;
   const std::string key("hey");
   keys.insert(key);
-  store()->Init(keys);
+  store()->Subscribe(keys);
 
   EXPECT_FALSE(Initialized());
   EXPECT_FALSE(observer()->initialized);
@@ -140,7 +145,7 @@
   std::set<std::string> keys;
   const std::string key("hey");
   keys.insert(key);
-  store()->Init(keys);
+  store()->Subscribe(keys);
 
   const int kValue = 42;
   base::FundamentalValue pref(kValue);
@@ -156,7 +161,7 @@
   std::set<std::string> keys;
   const std::string key("hey");
   keys.insert(key);
-  store()->Init(keys);
+  store()->Subscribe(keys);
 
   const int kValue = 42;
   base::FundamentalValue pref(kValue);
@@ -179,7 +184,7 @@
   const std::string key2("listen");
   keys.insert(key1);
   keys.insert(key2);
-  store()->Init(keys);
+  store()->Subscribe(keys);
 
   EXPECT_FALSE(Initialized());
   EXPECT_FALSE(observer()->initialized);
@@ -212,7 +217,7 @@
   std::set<std::string> keys;
   const std::string key("hey");
   keys.insert(key);
-  store()->Init(keys);
+  store()->Subscribe(keys);
 
   const std::string kInvalidKey("look");
   const int kValue = 42;
@@ -232,7 +237,7 @@
   const std::string key2("listen");
   keys.insert(key1);
   keys.insert(key2);
-  store()->Init(keys);
+  store()->Subscribe(keys);
 
   EXPECT_FALSE(Initialized());
   EXPECT_FALSE(observer()->initialized);
@@ -293,7 +298,7 @@
   const std::string key2("listen");
   keys.insert(key1);
   keys.insert(key2);
-  store()->Init(keys);
+  store()->Subscribe(keys);
 
   EXPECT_FALSE(Initialized());
   EXPECT_FALSE(observer()->initialized);
@@ -334,3 +339,54 @@
   EXPECT_EQ(1u, observer()->changed_keys.size());
   EXPECT_TRUE(manager()->set_preferences_called());
 }
+
+// Tests that a PrefObserverStore can subscribe multiple times to different
+// keys.
+TEST_F(PrefObserverStoreTest, MultipleSubscriptions) {
+  std::set<std::string> keys1;
+  const std::string key1("hey");
+  keys1.insert(key1);
+  store()->Subscribe(keys1);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_NE(manager()->last_preference_set().end(),
+            manager()->last_preference_set().find(key1));
+
+  std::set<std::string> keys2;
+  const std::string key2("listen");
+  keys2.insert(key2);
+  store()->Subscribe(keys2);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_NE(manager()->last_preference_set().end(),
+            manager()->last_preference_set().find(key2));
+}
+
+// Tests that multiple PrefStore::Observers can be added to a PrefObserverStore
+// and that they are each notified of changes.
+TEST_F(PrefObserverStoreTest, MultipleObservers) {
+  PrefStoreObserverMock observer2;
+  store()->AddObserver(&observer2);
+
+  std::set<std::string> keys;
+  const std::string key("hey");
+  keys.insert(key);
+  store()->Subscribe(keys);
+
+  const int kValue = 42;
+  base::FundamentalValue pref(kValue);
+  base::DictionaryValue prefs;
+  prefs.Set(key, pref.CreateDeepCopy());
+
+  // PreferenceManager notifies of PreferencesChanged, completing
+  // initialization.
+  OnPreferencesChanged(prefs);
+  EXPECT_TRUE(observer()->initialized);
+  EXPECT_TRUE(observer2.initialized);
+  EXPECT_TRUE(observer()->initialization_success);
+  EXPECT_TRUE(observer2.initialization_success);
+  observer()->VerifyAndResetChangedKey(key);
+  observer2.VerifyAndResetChangedKey(key);
+
+  store()->RemoveObserver(&observer2);
+}
+
+}  // namespace preferences
diff --git a/services/preferences/public/interfaces/preferences.mojom b/services/preferences/public/interfaces/preferences.mojom
index 0827850..27d43d31 100644
--- a/services/preferences/public/interfaces/preferences.mojom
+++ b/services/preferences/public/interfaces/preferences.mojom
@@ -6,6 +6,8 @@
 
 import "mojo/common/values.mojom";
 
+const string kServiceName = "preferences";
+
 // Used to subscribe to preference changes within PreferenceManager. After
 // requesting to observe, the current values for all requested keys are sent.
 interface PreferencesObserver {
@@ -15,6 +17,7 @@
 // Manages actual read/write of preference data. Accepts observers who subscribe
 // to preferences, notifying them of changes.
 interface PreferencesManager {
-  AddObserver(array<string> preferences, PreferencesObserver client);
+  AddObserver(PreferencesObserver client);
   SetPreferences(mojo.common.mojom.DictionaryValue preferences);
+  Subscribe(array<string> preferences);
 };
diff --git a/services/ui/service.cc b/services/ui/service.cc
index 66490be2..7139d26f 100644
--- a/services/ui/service.cc
+++ b/services/ui/service.cc
@@ -199,7 +199,8 @@
 
   ime_server_.Init(context()->connector(), test_config_);
 
-  discardable_memory::DiscardableSharedMemoryManager::CreateInstance();
+  discardable_shared_memory_manager_ =
+      base::MakeUnique<discardable_memory::DiscardableSharedMemoryManager>();
 }
 
 bool Service::OnConnect(const service_manager::ServiceInfo& remote_info,
@@ -368,8 +369,7 @@
 void Service::Create(
     const service_manager::Identity& remote_identity,
     discardable_memory::mojom::DiscardableSharedMemoryManagerRequest request) {
-  discardable_memory::DiscardableSharedMemoryManager::GetInstance()->Bind(
-      std::move(request));
+  discardable_shared_memory_manager_->Bind(std::move(request));
 }
 
 void Service::Create(const service_manager::Identity& remote_identity,
diff --git a/services/ui/service.h b/services/ui/service.h
index 7ae9c76..a33ce828 100644
--- a/services/ui/service.h
+++ b/services/ui/service.h
@@ -41,6 +41,10 @@
 #include "ui/ozone/public/client_native_pixmap_factory.h"
 #endif
 
+namespace discardable_memory {
+class DiscardableSharedMemoryManager;
+}
+
 namespace display {
 class ScreenManager;
 }
@@ -191,6 +195,9 @@
   IMERegistrarImpl ime_registrar_;
   IMEServerImpl ime_server_;
 
+  std::unique_ptr<discardable_memory::DiscardableSharedMemoryManager>
+      discardable_shared_memory_manager_;
+
   DISALLOW_COPY_AND_ASSIGN(Service);
 };
 
diff --git a/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter b/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter
index 7cae62b5..d68ce32 100644
--- a/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter
+++ b/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter
@@ -2,6 +2,8 @@
 
 # NavigationHandle::IsRendererInitiated some times return differently in
 # browser side navigation tests.
+-SBNavigationObserverBrowserTest.DirectDownloadNoReferrerTargetBlank
+-SBNavigationObserverBrowserTest.SingleMetaRefreshRedirectTargetBlank
 -SBNavigationObserverBrowserTest.NewTabDownload
 -SBNavigationObserverBrowserTest.NewTabDownloadWithDataURL
 -SBNavigationObserverBrowserTest.SubFrameNewTabDownload
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-browser-side-navigation b/third_party/WebKit/LayoutTests/FlagExpectations/enable-browser-side-navigation
index 875c42a..e01a7a0 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-browser-side-navigation
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-browser-side-navigation
@@ -139,12 +139,6 @@
 crbug.com/647698 http/tests/loading/307-after-303-after-post.html [ Failure ]
 crbug.com/647698 virtual/mojo-loading/http/tests/loading/307-after-303-after-post.html [ Failure ]
 
-# https://crbug.com/648608: Properly set the initator of the navigation.
-crbug.com/648608 http/tests/cookies/same-site/popup-same-site-post.html [ Failure ]
-crbug.com/648608 http/tests/cookies/same-site/popup-same-site.html [ Failure ]
-crbug.com/648608 virtual/mojo-loading/http/tests/cookies/same-site/popup-same-site-post.html [ Failure ]
-crbug.com/648608 virtual/mojo-loading/http/tests/cookies/same-site/popup-same-site.html [ Failure ]
-
 # https://crbug.com/673742 Calling document.open should cancel a load
 crbug.com/673742 fast/dom/Document/open-with-pending-load.html [ Failure ]
 
diff --git a/third_party/WebKit/LayoutTests/fast/block/positioning/fixed-position-with-relative-parent-expected.html b/third_party/WebKit/LayoutTests/fast/block/positioning/fixed-position-with-relative-parent-expected.html
new file mode 100644
index 0000000..b38ac94
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/block/positioning/fixed-position-with-relative-parent-expected.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<p>
+The test passes if the inner green rectangle covers the outer red one. There should be no red.
+</p>
+<div style="background: red; width: 70px; height: 70px; position: relative; left: 40px; top: 40px;">
+    <div style="background: green; width: 70px; height: 70px; position: absolute;"></div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/fast/block/positioning/fixed-position-with-relative-parent.html b/third_party/WebKit/LayoutTests/fast/block/positioning/fixed-position-with-relative-parent.html
new file mode 100644
index 0000000..f5a5886
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/block/positioning/fixed-position-with-relative-parent.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<p>
+The test passes if the inner green rectangle covers the outer red one. There should be no red.
+</p>
+<div style="background: red; width: 70px; height: 70px; position: relative; left: 40px; top: 40px;">
+    <div style="background: green; width: 70px; height: 70px; position: fixed;"></div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/fast/dom/HTMLDialogElement/fixpos-dialog-layout-expected.txt b/third_party/WebKit/LayoutTests/fast/dom/HTMLDialogElement/fixpos-dialog-layout-expected.txt
index 414acbb5..0c8ae9f4e 100644
--- a/third_party/WebKit/LayoutTests/fast/dom/HTMLDialogElement/fixpos-dialog-layout-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/dom/HTMLDialogElement/fixpos-dialog-layout-expected.txt
@@ -28,7 +28,7 @@
 PASS dialog.getBoundingClientRect().top is expectedTop
 
 Dialog should lose centering when removed from the document.
-FAIL dialog.getBoundingClientRect().top should be 0. Was 800.
+FAIL dialog.getBoundingClientRect().top should be 0. Was 820.
 
 Dialog's specified position should survive after close() and showModal().
 PASS dialog.getBoundingClientRect().top is expectedTop
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/fixed-margin-change-repaint-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/fixed-margin-change-repaint-expected.txt
index 10b9bc4..73ba874 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/fixed-margin-change-repaint-expected.txt
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/fixed-margin-change-repaint-expected.txt
@@ -8,12 +8,12 @@
       "paintInvalidations": [
         {
           "object": "LayoutBlockFlow (positioned) DIV id='test'",
-          "rect": [0, 20, 100, 100],
+          "rect": [0, 60, 100, 100],
           "reason": "bounds change"
         },
         {
           "object": "LayoutBlockFlow (positioned) DIV id='test'",
-          "rect": [0, 0, 100, 100],
+          "rect": [0, 40, 100, 100],
           "reason": "bounds change"
         }
       ]
diff --git a/third_party/WebKit/LayoutTests/platform/android/paint/invalidation/fixed-margin-change-repaint-expected.txt b/third_party/WebKit/LayoutTests/platform/android/paint/invalidation/fixed-margin-change-repaint-expected.txt
index d1f7007..a6a11fc 100644
--- a/third_party/WebKit/LayoutTests/platform/android/paint/invalidation/fixed-margin-change-repaint-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/android/paint/invalidation/fixed-margin-change-repaint-expected.txt
@@ -6,8 +6,8 @@
       "contentsOpaque": true,
       "drawsContent": true,
       "repaintRects": [
-        [0, 20, 100, 100],
-        [0, 0, 100, 100]
+        [0, 60, 100, 100],
+        [0, 40, 100, 100]
       ],
       "paintInvalidationClients": [
         "LayoutBlockFlow (positioned) DIV id='test'"
diff --git a/third_party/WebKit/LayoutTests/svg/dom/svg-root-lengths-expected.txt b/third_party/WebKit/LayoutTests/svg/dom/svg-root-lengths-expected.txt
index 85cc4238..f21d97b1 100644
--- a/third_party/WebKit/LayoutTests/svg/dom/svg-root-lengths-expected.txt
+++ b/third_party/WebKit/LayoutTests/svg/dom/svg-root-lengths-expected.txt
@@ -3,10 +3,7 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
- PASS successfullyParsed is true
-
-TEST COMPLETE
-Initial/default values:
+ Initial/default values:
 PASS svg.width.baseVal.value is 200
 PASS svg.height.baseVal.value is 200
 
@@ -21,4 +18,7 @@
 viewBox has no effect on top level length resolution.
 PASS svg.width.baseVal.value is 200
 PASS svg.height.baseVal.value is 100
+PASS successfullyParsed is true
+
+TEST COMPLETE
 
diff --git a/third_party/WebKit/LayoutTests/svg/dom/svg-root-lengths.html b/third_party/WebKit/LayoutTests/svg/dom/svg-root-lengths.html
index 31d53f5..dd223a8 100644
--- a/third_party/WebKit/LayoutTests/svg/dom/svg-root-lengths.html
+++ b/third_party/WebKit/LayoutTests/svg/dom/svg-root-lengths.html
@@ -17,41 +17,42 @@
 	testRunner.dumpAsText();
       }
 
-      setTimeout(function () {
-	var div = document.getElementById('div');
-        var svg = document.getElementById('svg');
+      // Force layout
+      document.body.offsetTop;
 
-        description('This tests the behavior of root SVG length value resolution');
+      var div = document.getElementById('div');
+      var svg = document.getElementById('svg');
 
-        debug('Initial/default values:');
-        shouldBe('svg.width.baseVal.value', '200');
-        shouldBe('svg.height.baseVal.value', '200');
+      description('This tests the behavior of root SVG length value resolution');
 
-	svg.setAttribute('width', '50%');
-	svg.setAttribute('height', '10%');
-	debug('');
-	debug('Updated relative values:');
-        shouldBe('svg.width.baseVal.value', '100');
-        shouldBe('svg.height.baseVal.value', '20');
+      debug('Initial/default values:');
+      shouldBe('svg.width.baseVal.value', '200');
+      shouldBe('svg.height.baseVal.value', '200');
 
-	svg.setAttribute('width', '150');
-	svg.setAttribute('height', '50');
-	debug('');
-	debug('Updated fixed values:');
-        shouldBe('svg.width.baseVal.value', '150');
-        shouldBe('svg.height.baseVal.value', '50');
+      svg.setAttribute('width', '50%');
+      svg.setAttribute('height', '10%');
+      debug('');
+      debug('Updated relative values:');
+      shouldBe('svg.width.baseVal.value', '100');
+      shouldBe('svg.height.baseVal.value', '20');
 
-	svg.setAttribute('width', '100%');
-	svg.setAttribute('height', '50%');
-	svg.setAttribute('viewBox', '0 0 800 600');
-        debug('');
-	debug('viewBox has no effect on top level length resolution.');
-        shouldBe('svg.width.baseVal.value', '200');
-        shouldBe('svg.height.baseVal.value', '100');
+      svg.setAttribute('width', '150');
+      svg.setAttribute('height', '50');
+      debug('');
+      debug('Updated fixed values:');
+      shouldBe('svg.width.baseVal.value', '150');
+      shouldBe('svg.height.baseVal.value', '50');
 
-        if (window.testRunner)
-          testRunner.notifyDone();
-      }, 0);
+      svg.setAttribute('width', '100%');
+      svg.setAttribute('height', '50%');
+      svg.setAttribute('viewBox', '0 0 800 600');
+      debug('');
+      debug('viewBox has no effect on top level length resolution.');
+      shouldBe('svg.width.baseVal.value', '200');
+      shouldBe('svg.height.baseVal.value', '100');
+
+      if (window.testRunner)
+        testRunner.notifyDone();
     </script>
   </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-byte-data-expected.txt b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-byte-data-expected.txt
deleted file mode 100644
index cf3ed0c..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-byte-data-expected.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-Test AnalyserNode getByteTimeDomainData
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-PASS Index of first sample greater than +1 is greater than or equal to 0.
-PASS Index of first sample less than -1 is greater than or equal to 0.
-PASS Clip  1.01149: byteData[43] is equal to 255.
-PASS Clip -1.42637: byteData[56] is equal to 0.
-PASS Byte data is identical to the array [128,130,135,136,140,143,145,149,152,153,159,160,163,167,168,173,...].
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-byte-data.html b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-byte-data.html
index ee781789..a8d1e1b 100644
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-byte-data.html
+++ b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-byte-data.html
@@ -1,7 +1,8 @@
 <!doctype html>
 <html>
   <head>
-    <script src="../../resources/js-test.js"></script>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script> 
     <script src="../resources/compatibility.js"></script>
     <script src="../resources/audit-util.js"></script>
     <script src="../resources/audio-testing.js"></script>
@@ -10,9 +11,6 @@
 
   <body>
     <script>
-      description("Test AnalyserNode getByteTimeDomainData");
-      window.jsTestIsAsync = true;
-
       var sampleRate = 48000;
       // The size of the analyser frame.  Anything larger than 128 is ok, but should be long enough
       // to capture the peaks of the oscillator waveform.
@@ -94,7 +92,6 @@
       });
 
       audit.defineTask("finish", function (done) {
-        finishJSTest();
         done();
       });
 
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-downmix-expected.txt b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-downmix-expected.txt
deleted file mode 100644
index 6e4a587..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-downmix-expected.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-Test AnalyserNode Downmixing
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-PASS Analyser downmix mono to mono time data is identical to the array [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,...].
-PASS Analyser downmix mono to mono freq data equals [-7.53501,-12.0412,-27.9588,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 6.3283e-8}.
-PASS Analyser downmix mono to mono downmixed correctly.
-
-PASS Analyser downmix stereo to mono time data is identical to the array [2.5,2.5,2.5,2.5,2.5,2.5,2.5,2.5,2.5,2.5,2.5,2.5,2.5,2.5,2.5,2.5,...].
-PASS Analyser downmix stereo to mono freq data equals [0.423786,-4.08240,-20.0000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 1.1681e-7}.
-PASS Analyser downmix stereo to mono downmixed correctly.
-
-PASS Analyser downmix quad to mono time data is identical to the array [7.5,7.5,7.5,7.5,7.5,7.5,7.5,7.5,7.5,7.5,7.5,7.5,7.5,7.5,7.5,7.5,...].
-PASS Analyser downmix quad to mono freq data equals [9.96621,5.46002,-10.4576,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 4.9793e-7}.
-PASS Analyser downmix quad to mono downmixed correctly.
-
-PASS Analyser downmix 5.1 to mono time data is identical to the array [43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,43.0355339050293,...].
-PASS Analyser downmix 5.1 to mono freq data equals [25.1415,20.6353,4.71774,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 2.0215e-7}.
-PASS Analyser downmix 5.1 to mono downmixed correctly.
-
-PASS Analyser downmix 3-channel to mono time data is identical to the array [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,...].
-PASS Analyser downmix 3-channel to mono freq data equals [-7.53501,-12.0412,-27.9588,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,-100.000,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 6.3283e-8}.
-PASS Analyser downmix 3-channel to mono downmixed correctly.
-
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-downmix.html b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-downmix.html
index 2df48d4..6f919b0 100644
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-downmix.html
+++ b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-downmix.html
@@ -1,7 +1,8 @@
 <!doctype html>
 <html>
   <head>
-    <script src="../../resources/js-test.js"></script>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script> 
     <script src="../resources/compatibility.js"></script>
     <script src="../resources/audit-util.js"></script>
     <script src="../resources/audio-testing.js"></script>
@@ -12,9 +13,6 @@
 
   <body>
     <script>
-      description("Test AnalyserNode Downmixing");
-      window.jsTestIsAsync = true;
-
       var sampleRate = 44100;
       var renderFrames = 2048;
 
@@ -52,7 +50,6 @@
       }
 
       audit.defineTask("finish", function (done) {
-        finishJSTest();
         done();
       });
 
@@ -115,10 +112,9 @@
             floatRelError: options.floatRelError,
           }) && success;
 
-          if (success)
-            testPassed(prefix + " downmixed correctly.\n");
-          else
-            testFailed(prefix + " not downmixed correctly.\n");
+          Should(prefix, success)
+            .summarize("downmixed correctly.",
+                       "no downmixed correctly.");
         });
       }
     </script>
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-float-data-expected.txt b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-float-data-expected.txt
deleted file mode 100644
index 9106477..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-float-data-expected.txt
+++ /dev/null
@@ -1,64 +0,0 @@
-Test AnalyserNode getFloatTimeDomainData
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-PASS 8-element short array initialized to Infinity.
-PASS getFloatTimeDomainData(<8-element vector>).
-PASS 8-element time domain data is identical to the array [97,98,99,100,101,102,103,104].
-PASS 64-element long array initialized to Infinity.
-PASS getFloatTimeDomainData(<64-element vector>).
-PASS longData.subarray(0, 32) is identical to the array [97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128].
-PASS Unfilled elements longData.subarray(32) contains only the constant Infinity.
-PASS Long and short time domain arrays handled correctly.
-
-PASS 32-point analyser time domain data is identical to the array [32737,32738,32739,32740,32741,32742,32743,32744,32745,32746,32747,32748,32749,32750,32751,32752,...].
-PASS 64-point analyser time domain data is identical to the array [32705,32706,32707,32708,32709,32710,32711,32712,32713,32714,32715,32716,32717,32718,32719,32720,...].
-PASS 128-point analyser time domain data is identical to the array [32641,32642,32643,32644,32645,32646,32647,32648,32649,32650,32651,32652,32653,32654,32655,32656,...].
-PASS 256-point analyser time domain data is identical to the array [32513,32514,32515,32516,32517,32518,32519,32520,32521,32522,32523,32524,32525,32526,32527,32528,...].
-PASS 512-point analyser time domain data is identical to the array [32257,32258,32259,32260,32261,32262,32263,32264,32265,32266,32267,32268,32269,32270,32271,32272,...].
-PASS 1024-point analyser time domain data is identical to the array [31745,31746,31747,31748,31749,31750,31751,31752,31753,31754,31755,31756,31757,31758,31759,31760,...].
-PASS 2048-point analyser time domain data is identical to the array [30721,30722,30723,30724,30725,30726,30727,30728,30729,30730,30731,30732,30733,30734,30735,30736,...].
-PASS 4096-point analyser time domain data is identical to the array [28673,28674,28675,28676,28677,28678,28679,28680,28681,28682,28683,28684,28685,28686,28687,28688,...].
-PASS 8192-point analyser time domain data is identical to the array [24577,24578,24579,24580,24581,24582,24583,24584,24585,24586,24587,24588,24589,24590,24591,24592,...].
-PASS 16384-point analyser time domain data is identical to the array [16385,16386,16387,16388,16389,16390,16391,16392,16393,16394,16395,16396,16397,16398,16399,16400,...].
-PASS 32768-point analyser time domain data is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS Time domain data contained the correct data for each size.
-
-PASS At frame 127: data.subarray(0, 1920) contains only the constant 0.
-PASS At frame 127: data.subarray(1920, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 255: data.subarray(0, 1792) contains only the constant 0.
-PASS At frame 255: data.subarray(1792, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 383: data.subarray(0, 1664) contains only the constant 0.
-PASS At frame 383: data.subarray(1664, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 511: data.subarray(0, 1536) contains only the constant 0.
-PASS At frame 511: data.subarray(1536, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 639: data.subarray(0, 1408) contains only the constant 0.
-PASS At frame 639: data.subarray(1408, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 767: data.subarray(0, 1280) contains only the constant 0.
-PASS At frame 767: data.subarray(1280, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 895: data.subarray(0, 1152) contains only the constant 0.
-PASS At frame 895: data.subarray(1152, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 1023: data.subarray(0, 1024) contains only the constant 0.
-PASS At frame 1023: data.subarray(1024, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 1151: data.subarray(0, 896) contains only the constant 0.
-PASS At frame 1151: data.subarray(896, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 1279: data.subarray(0, 768) contains only the constant 0.
-PASS At frame 1279: data.subarray(768, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 1407: data.subarray(0, 640) contains only the constant 0.
-PASS At frame 1407: data.subarray(640, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 1535: data.subarray(0, 512) contains only the constant 0.
-PASS At frame 1535: data.subarray(512, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 1663: data.subarray(0, 384) contains only the constant 0.
-PASS At frame 1663: data.subarray(384, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 1791: data.subarray(0, 256) contains only the constant 0.
-PASS At frame 1791: data.subarray(256, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 1919: data.subarray(0, 128) contains only the constant 0.
-PASS At frame 1919: data.subarray(128, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS At frame 2047: data.subarray(0, 2048) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,...].
-PASS Time domain data contained initial zeroes and correct data as expected.
-
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-float-data.html b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-float-data.html
index 810d530c..19fbd104 100644
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-float-data.html
+++ b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-float-data.html
@@ -1,7 +1,8 @@
 <!doctype html>
 <html>
   <head>
-    <script src="../../resources/js-test.js"></script>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script> 
     <script src="../resources/compatibility.js"></script>
     <script src="../resources/audit-util.js"></script>
     <script src="../resources/audio-testing.js"></script>
@@ -10,9 +11,6 @@
 
   <body>
     <script>
-      description("Test AnalyserNode getFloatTimeDomainData");
-      window.jsTestIsAsync = true;
-
       // Use a power of two to eliminate any round-off in the computation of the times for
       // context.suspend().
       var sampleRate = 32768;
@@ -40,10 +38,8 @@
           var shortData = new Float32Array(8);
           // Initialize the array to Infinity to represent uninitialize data.
           shortData.fill(Infinity);
-          testPassed(shortData.length + "-element short array initialized to Infinity.");
 
           analyser.getFloatTimeDomainData(shortData);
-          testPassed("getFloatTimeDomainData(<" + shortData.length + "-element vector>).");
 
           // The short array should be filled with the expected data, with no errors thrown.
 
@@ -54,10 +50,8 @@
           var longData = new Float32Array(2 * fftSize);
           // Initialize the array to Infinity to represent uninitialize data.
           longData.fill(Infinity);
-          testPassed(longData.length + "-element long array initialized to Infinity.");
 
           analyser.getFloatTimeDomainData(longData);
-          testPassed("getFloatTimeDomainData(<" + longData.length + "-element vector>).");
 
           // The long array should filled with the expected data but the extra elements should be
           // untouched.
@@ -73,10 +67,8 @@
         }).then(context.resume.bind(context));
 
         context.startRendering().then(function (buffer) {
-          if (success)
-            testPassed("Long and short time domain arrays handled correctly.\n");
-          else
-            testFailed("Long and short time domain arrays handled incorrectly.\n");
+          Should("Long and short time domain arrays handled", success)
+            .summarize("correctly.", "incorrectly.");
         }).then(done);
       });
 
@@ -94,10 +86,9 @@
       }
 
       audit.defineTask("summarize size tests", function (done) {
-        if (success)
-          testPassed("Time domain data contained the correct data for each size.\n");
-        else
-          testFailed("Time domain data did not contain the correct data for each size.\n");
+        Should("Time domain data", success)
+          .summarize("contained the correct data for each size.",
+                     "did not contain the correct data for each size.");
 
         done();
       });
@@ -139,20 +130,14 @@
         }
 
         context.startRendering().then(function (b) {
-          if (success) {
-            testPassed(
-              "Time domain data contained initial zeroes and correct data as expected.\n");
-          } else {
-            testFailed(
-              "Time domain data did not contain initial zeroes and correct data as expected.\n"
-            );
-          }
-
+          Should("Time domain data", success)
+            .summarize(
+              "contained initial zeroes and correct data as expected",
+              "did not contain initial zeroes and correct data as expected.");
         }).then(done);
       });
 
       audit.defineTask("finish", function (done) {
-        finishJSTest();
         done();
       });
 
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-expected.txt b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-expected.txt
deleted file mode 100644
index 9c929923..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-expected.txt
+++ /dev/null
@@ -1,58 +0,0 @@
-Test AnalyserNode getFloatFrequencyData and getByteFrequencyData, no Smoothing
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-PASS 32-point float FFT equals [-38.0,-14.8,-15.6,-19.9,-22.9,-25.1,-26.7,-28.1,-29.3,-30.4,-31.7,-33.7,-36.0,-38.5,-43.7,-55.3] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 6.8964e-7}.
-PASS Min FFT value is less than or equal to -50.
-PASS Max FFT value is greater than or equal to -30.
-PASS 32-point byte FFT equals [153,255,255,255,255,255,255,255,255,249,233,207,178,146,79,0] with an element-wise tolerance of 0.
-PASS 64-point float FFT equals [-15.9,-17.2,-23.0,-30.0,-32.4,-34.3,-35.9,-37.3,-38.4,-39.5,-40.4,-41.2,-41.9,-42.7,-43.3,-43.9,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 0.0000068366}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 64-point byte FFT equals [255,255,255,254,246,239,233,228,224,220,217,214,211,208,206,204,...] with an element-wise tolerance of 0.
-PASS 128-point float FFT equals [-29.5,-21.3,-20.2,-27.9,-29.0,-31.4,-34.1,-33.5,-37.0,-36.2,-37.6,-39.2,-38.3,-41.4,-39.7,-41.7,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 0.0000014602}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 128-point byte FFT equals [255,255,255,255,255,249,239,242,229,232,227,221,224,213,219,212,...] with an element-wise tolerance of 0.
-PASS 256-point float FFT equals [-72.0,-53.6,-28.6,-19.7,-20.3,-29.9,-28.3,-25.0,-30.6,-35.8,-28.9,-30.5,-39.9,-33.4,-31.2,-37.9,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 8.4828e-7}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 256-point byte FFT equals [102,169,255,255,255,255,255,255,252,233,255,253,218,242,250,226,...] with an element-wise tolerance of 0.
-PASS 512-point float FFT equals [-99.7,-95.6,-90.0,-86.2,-82.1,-36.4,-22.3,-19.0,-24.7,-42.6,-83.7,-79.3,-39.9,-27.4,-25.2,-32.1,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 0.000023906}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 512-point byte FFT equals [0,16,36,50,65,231,255,255,255,208,59,75,219,255,255,247,...] with an element-wise tolerance of 0.
-PASS 1024-point float FFT equals [-100,-100,-100,-100,-99.4,-96.6,-93.6,-90.2,-86.4,-82.5,-79.7,-70.4,-33.9,-21.4,-19.2,-26.1,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 0.000020483}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 1024-point byte FFT equals [0,0,0,0,2,12,23,35,49,63,74,107,240,255,255,255,...] with an element-wise tolerance of 0.
-PASS 2048-point float FFT equals [-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 0.000013456}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 2048-point byte FFT equals [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...] with an element-wise tolerance of 0.
-PASS 4096-point float FFT equals [-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 4.6116e-7}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 4096-point byte FFT equals [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...] with an element-wise tolerance of 0.
-PASS 8192-point float FFT equals [-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 3.2106e-7}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 8192-point byte FFT equals [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...] with an element-wise tolerance of 0.
-PASS 16384-point float FFT equals [-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 1.1756e-7}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 16384-point byte FFT equals [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...] with an element-wise tolerance of 0.
-PASS 32768-point float FFT equals [-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,-100,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 1.1756e-7}.
-PASS Min FFT value is less than or equal to -100.
-PASS Max FFT value is greater than or equal to -30.
-PASS 32768-point byte FFT equals [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...] with an element-wise tolerance of 0.
-PASS Basic frequency data computed correctly.
-
-PASS 128-point float FFT equals [-33.7,-21.1,-18.0,-20.2,-23.2,-25.3,-27.0,-28.4,-29.6,-30.7,-31.6,-32.4,-33.2,-33.9,-34.5,-35.2,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 0.0000012548}.
-PASS Smoothing constant of 0 correctly handled.
-
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-smoothing-expected.txt b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-smoothing-expected.txt
deleted file mode 100644
index 48f4054..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-smoothing-expected.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-Test AnalyserNode getFloatFrequencyData and getByteFrequencyData, Smoothing
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-PASS First 512-point FFT at frame 512 equals [-100,-100,-96.1,-92.2,-88.1,-42.5,-28.3,-25.0,-30.7,-48.7,-89.7,-85.4,-45.9,-33.4,-31.2,-38.1,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 0.0000059207}.
-PASS 512-point byte FFT equals [0,0,14,28,43,209,255,255,252,186,37,53,197,242,250,225,...] with an element-wise tolerance of 0.
-PASS Smoothed 512-point FFT at frame 1536 equals [-94.6,-93.1,-90.1,-87.2,-85.6,-38.9,-24.8,-21.5,-27.2,-45.1,-87.5,-78.7,-42.4,-29.9,-27.7,-34.6,...] with an element-wise tolerance of {absoluteThreshold: 0, relativeThreshold: 0.000025332}.
-PASS 512-point byte FFT equals [19,25,36,46,52,222,255,255,255,199,45,77,209,255,255,238,...] with an element-wise tolerance of 0.
-PASS FFT smoothing performed correctly with smoothing constant 0.5.
-
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-smoothing.html b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-smoothing.html
index 2bf4791..ef2e4dd4 100644
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-smoothing.html
+++ b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data-smoothing.html
@@ -1,7 +1,8 @@
 <!doctype html>
 <html>
   <head>
-    <script src="../../resources/js-test.js"></script>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script> 
     <script src="../resources/compatibility.js"></script>
     <script src="../resources/audit-util.js"></script>
     <script src="../resources/audio-testing.js"></script>
@@ -13,9 +14,6 @@
 
   <body>
     <script>
-      description("Test AnalyserNode getFloatFrequencyData and getByteFrequencyData, Smoothing");
-      window.jsTestIsAsync = true;
-
       // Use a power of two to eliminate any round-off in the computation of the times for
       // context.suspend().
       var sampleRate = 32768;
@@ -117,18 +115,16 @@
         }).then(context.resume.bind(context));
 
         context.startRendering().then(function (buffer) {
-          var prefix = "FFT smoothing performed ";
-          var suffix = " with smoothing constant " + analyser.smoothingTimeConstant + ".\n"
+          var prefix = "FFT smoothing performed";
+          var suffix = " with smoothing constant " + analyser.smoothingTimeConstant;
 
-          if (success)
-            testPassed(prefix + "correctly" + suffix);
-          else
-            testFailed(prefix + "incorrectly" + suffix);
+          Should(prefix, success)
+            .summarize("correctly" + suffix,
+                       "incorrectly" + suffix);
         }).then(done);
       });
 
       audit.defineTask("finish", function (done) {
-        finishJSTest();
         done();
       });
 
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data.html b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data.html
index 8228406..cb9e3bea 100644
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data.html
+++ b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-freq-data.html
@@ -1,7 +1,8 @@
 <!doctype html>
 <html>
   <head>
-    <script src="../../resources/js-test.js"></script>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script> 
     <script src="../resources/compatibility.js"></script>
     <script src="../resources/audit-util.js"></script>
     <script src="../resources/audio-testing.js"></script>
@@ -12,9 +13,6 @@
 
   <body>
     <script>
-      description("Test AnalyserNode getFloatFrequencyData and getByteFrequencyData, no Smoothing");
-      window.jsTestIsAsync = true;
-
       // Use a power of two to eliminate any round-off in the computation of the times for
       // context.suspend().
       var sampleRate = 32768;
@@ -82,10 +80,8 @@
 
       // Just print a summary of the result of the above tests.
       audit.defineTask("summarize basic tests", function (done) {
-        if (basicTestsPassed)
-          testPassed("Basic frequency data computed correctly.\n");
-        else
-          testFailed("Basic frequency data computed incorrectly.\n");
+        Should("Basic frequency data computed", basicTestsPassed)
+          .summarize("correctly", "incorrectly");
         done();
       });
 
@@ -132,17 +128,14 @@
             freqData, expected, options);
           basicTestsPassed = basicTestsPassed && comparison.success;
 
-          if (comparison.success)
-            testPassed("Smoothing constant of 0 correctly handled.\n");
-          else
-            testFailed("Smoothing constant of 0 incorrectly handled.\n");
+          Should("Smoothing constant of 0", comparison.success)
+            .summarize("correctly handled", "incorrectly handled");
         }).then(context.resume.bind(context));
 
         context.startRendering().then(done);
       });
 
       audit.defineTask("finish", function (done) {
-        finishJSTest();
         done();
       });
 
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-multiple-calls-expected.txt b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-multiple-calls-expected.txt
deleted file mode 100644
index 682f0f8..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-multiple-calls-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-Test AnalyserNode getFloatFrequencyData and getByteFrequencyData
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-PASS Second call to getFloatFrequencyData is identical to the array [-58.539,-41.299,-33.422,-35.230,-42.020,-39.602,-46.787,-42.696,-47.643,-46.141,-47.208,-50.287,-47.460,-53.708,-48.735,-53.302,...].
-PASS Second call to getByteFrequencyData is identical to the array [167,232,255,255,238,239,223,228,216,217,214,206,211,197,206,196,...].
-PASS Output of getByteFrequencyData after getFloatFrequencyData is identical to the array [163,242,255,255,252,250,237,238,229,228,225,219,221,212,216,209,...].
-PASS Output of getFloatFrequenycData (converted to byte) after getByteFrequencyData is identical to the array [176,248,255,255,255,255,243,244,235,234,231,225,227,217,222,215,...].
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-multiple-calls.html b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-multiple-calls.html
index 4516d26..40dee1d 100644
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-multiple-calls.html
+++ b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-multiple-calls.html
@@ -1,7 +1,8 @@
 <!doctype html>
 <html>
   <head>
-    <script src="../../resources/js-test.js"></script>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script> 
     <script src="../resources/compatibility.js"></script>
     <script src="../resources/audit-util.js"></script>
     <script src="../resources/audio-testing.js"></script>
@@ -10,9 +11,6 @@
 
   <body>
     <script>
-      description("Test AnalyserNode getFloatFrequencyData and getByteFrequencyData");
-      window.jsTestIsAsync = true;
-
       var sampleRate = 48000;
       // Render enough data to run the test.  
       var renderFrames = 2*1024;
@@ -107,7 +105,6 @@
       });
 
       audit.defineTask("finish", function (done) {
-        finishJSTest();
         done();
       });
 
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-zero-expected.txt b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-zero-expected.txt
deleted file mode 100644
index 6991218..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-zero-expected.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-Test AnalyserNode getFloatFrequencyData With Zero-Valued Input
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-PASS getFloatFrequencyData() with zero-valued input contains only the constant -Infinity.
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-zero.html b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-zero.html
index 31afeb0..c5776e7 100644
--- a/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-zero.html
+++ b/third_party/WebKit/LayoutTests/webaudio/Analyser/realtimeanalyser-zero.html
@@ -1,7 +1,8 @@
 <!doctype html>
 <html>
   <head>
-    <script src="../../resources/js-test.js"></script>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script> 
     <script src="../resources/compatibility.js"></script>
     <script src="../resources/audit-util.js"></script>
     <script src="../resources/audio-testing.js"></script>
@@ -10,9 +11,6 @@
 
   <body>
     <script>
-      description("Test AnalyserNode getFloatFrequencyData With Zero-Valued Input");
-      window.jsTestIsAsync = true;
-
       var sampleRate = 48000;
 
       // Render enough data to run the test.  
@@ -53,7 +51,6 @@
       });
       
       audit.defineTask("finish", function (done) {
-        finishJSTest();
         done();
       });
 
diff --git a/third_party/WebKit/Source/core/frame/FrameView.cpp b/third_party/WebKit/Source/core/frame/FrameView.cpp
index 9ea6758d..08a44f70 100644
--- a/third_party/WebKit/Source/core/frame/FrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/FrameView.cpp
@@ -316,6 +316,8 @@
   m_visibilityObserver = new ElementVisibilityObserver(
       targetElement, WTF::bind(
                          [](FrameView* frameView, bool isVisible) {
+                           if (!frameView)
+                             return;
                            frameView->updateRenderThrottlingStatus(
                                !isVisible, frameView->m_subtreeThrottled);
                            frameView->maybeRecordLoadReason();
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.cpp b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
index dd6a22b..fb3c6cb 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBox.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
@@ -4170,6 +4170,8 @@
       continue;
     const LayoutBox& box = *toLayoutBox(curr);
     staticLogicalTop += box.logicalTop();
+    if (box.isInFlowPositioned())
+      staticLogicalTop += box.offsetForInFlowPosition().height();
     if (!box.isLayoutFlowThread())
       continue;
     // We're walking out of a flowthread here. This flow thread is not in the
diff --git a/third_party/WebKit/Source/core/layout/LayoutThemeDefault.cpp b/third_party/WebKit/Source/core/layout/LayoutThemeDefault.cpp
index 39e0c09..53356ee 100644
--- a/third_party/WebKit/Source/core/layout/LayoutThemeDefault.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutThemeDefault.cpp
@@ -335,22 +335,22 @@
   return menuListInternalPadding(style, 1);
 }
 
-int LayoutThemeDefault::scrollbarThicknessInDIP() const {
-  if (m_scrollbarThicknessInDIP > 0)
-    return m_scrollbarThicknessInDIP;
+int LayoutThemeDefault::menuListArrowWidthInDIP() const {
+  if (m_menuListArrowWidthInDIP > 0)
+    return m_menuListArrowWidthInDIP;
   int width = Platform::current()
                   ->themeEngine()
                   ->getSize(WebThemeEngine::PartScrollbarUpArrow)
                   .width;
-  const_cast<LayoutThemeDefault*>(this)->m_scrollbarThicknessInDIP =
+  const_cast<LayoutThemeDefault*>(this)->m_menuListArrowWidthInDIP =
       width > 0 ? width : 15;
-  return m_scrollbarThicknessInDIP;
+  return m_menuListArrowWidthInDIP;
 }
 
 float LayoutThemeDefault::clampedMenuListArrowPaddingSize(
     const HostWindow* host,
     const ComputedStyle& style) const {
-  int originalSize = scrollbarThicknessInDIP();
+  int originalSize = menuListArrowWidthInDIP();
   int scaledSize =
       host ? host->windowToViewportScalar(originalSize) : originalSize;
   // The result should not be samller than the scrollbar thickness in order to
diff --git a/third_party/WebKit/Source/core/layout/LayoutThemeDefault.h b/third_party/WebKit/Source/core/layout/LayoutThemeDefault.h
index 874733a..498edfd 100644
--- a/third_party/WebKit/Source/core/layout/LayoutThemeDefault.h
+++ b/third_party/WebKit/Source/core/layout/LayoutThemeDefault.h
@@ -115,7 +115,11 @@
                               const ComputedStyle&) const override;
   int popupInternalPaddingTop(const ComputedStyle&) const override;
   int popupInternalPaddingBottom(const ComputedStyle&) const override;
-  int scrollbarThicknessInDIP() const;
+  // This returns a value based on scrollbar thickness.  It's not 0 even in
+  // overlay scrollbar mode.  On Android, this doesn't match to scrollbar
+  // thickness, which is 3px or 4px, and we use the value from the default Aura
+  // theme.
+  int menuListArrowWidthInDIP() const;
   float clampedMenuListArrowPaddingSize(const HostWindow*,
                                         const ComputedStyle&) const;
 
@@ -155,7 +159,7 @@
   static unsigned m_inactiveSelectionForegroundColor;
 
   ThemePainterDefault m_painter;
-  int m_scrollbarThicknessInDIP = 0;
+  int m_menuListArrowWidthInDIP = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/ThemePainterDefault.cpp b/third_party/WebKit/Source/core/paint/ThemePainterDefault.cpp
index a41ddf4f..57004b2 100644
--- a/third_party/WebKit/Source/core/paint/ThemePainterDefault.cpp
+++ b/third_party/WebKit/Source/core/paint/ThemePainterDefault.cpp
@@ -290,7 +290,7 @@
   extraParams.menuList.arrowY = middle;
   float arrowBoxWidth = m_theme.clampedMenuListArrowPaddingSize(
       box.frameView()->getHostWindow(), box.styleRef());
-  float arrowScaleFactor = arrowBoxWidth / m_theme.scrollbarThicknessInDIP();
+  float arrowScaleFactor = arrowBoxWidth / m_theme.menuListArrowWidthInDIP();
   if (useMockTheme()) {
     // The size and position of the drop-down button is different between
     // the mock theme and the regular aura theme.
diff --git a/third_party/WebKit/Source/modules/fetch/BytesConsumer.cpp b/third_party/WebKit/Source/modules/fetch/BytesConsumer.cpp
index 3dcad3c..429b3eb 100644
--- a/third_party/WebKit/Source/modules/fetch/BytesConsumer.cpp
+++ b/third_party/WebKit/Source/modules/fetch/BytesConsumer.cpp
@@ -26,12 +26,12 @@
   void onStateChange() override {}
 };
 
-class Tee final : public GarbageCollectedFinalized<Tee>,
-                  public BytesConsumer::Client {
-  USING_GARBAGE_COLLECTED_MIXIN(Tee);
+class TeeHelper final : public GarbageCollectedFinalized<TeeHelper>,
+                        public BytesConsumer::Client {
+  USING_GARBAGE_COLLECTED_MIXIN(TeeHelper);
 
  public:
-  Tee(ExecutionContext* executionContext, BytesConsumer* consumer)
+  TeeHelper(ExecutionContext* executionContext, BytesConsumer* consumer)
       : m_src(consumer),
         m_destination1(new Destination(executionContext, this)),
         m_destination2(new Destination(executionContext, this)) {
@@ -126,7 +126,7 @@
 
   class Destination final : public BytesConsumer {
    public:
-    Destination(ExecutionContext* executionContext, Tee* tee)
+    Destination(ExecutionContext* executionContext, TeeHelper* tee)
         : m_executionContext(executionContext), m_tee(tee) {}
 
     Result beginRead(const char** buffer, size_t* available) override {
@@ -218,7 +218,7 @@
 
     Error getError() const override { return m_tee->getError(); }
 
-    String debugName() const override { return "Tee::Destination"; }
+    String debugName() const override { return "TeeHelper::Destination"; }
 
     void enqueue(Chunk* chunk) {
       if (m_isCancelled)
@@ -277,7 +277,7 @@
     }
 
     Member<ExecutionContext> m_executionContext;
-    Member<Tee> m_tee;
+    Member<TeeHelper> m_tee;
     Member<BytesConsumer::Client> m_client;
     HeapDeque<Member<Chunk>> m_chunks;
     Member<Chunk> m_chunkInUse;
@@ -362,7 +362,7 @@
     return;
   }
 
-  Tee* tee = new Tee(executionContext, src);
+  TeeHelper* tee = new TeeHelper(executionContext, src);
   *dest1 = tee->destination1();
   *dest2 = tee->destination2();
 }
diff --git a/third_party/WebKit/Source/platform/audio/AudioDestination.cpp b/third_party/WebKit/Source/platform/audio/AudioDestination.cpp
index 448ef74..7244418 100644
--- a/third_party/WebKit/Source/platform/audio/AudioDestination.cpp
+++ b/third_party/WebKit/Source/platform/audio/AudioDestination.cpp
@@ -64,8 +64,6 @@
                                    PassRefPtr<SecurityOrigin> securityOrigin)
     : m_callback(callback),
       m_numberOfOutputChannels(numberOfOutputChannels),
-      m_inputBus(AudioBus::create(numberOfInputChannels,
-                                  AudioUtilities::kRenderQuantumFrames)),
       m_renderBus(AudioBus::create(numberOfOutputChannels,
                                    AudioUtilities::kRenderQuantumFrames,
                                    false)),
@@ -130,19 +128,6 @@
   m_fifo =
       WTF::wrapUnique(new AudioPullFIFO(*this, numberOfOutputChannels, fifoSize,
                                         AudioUtilities::kRenderQuantumFrames));
-
-  // Input buffering.
-  m_inputFifo = WTF::makeUnique<AudioFIFO>(numberOfInputChannels, fifoSize);
-
-  // If the callback size does not match the render size, then we need to
-  // buffer some extra silence for the input. Otherwise, we can over-consume
-  // the input FIFO.
-  if (m_callbackBufferSize != AudioUtilities::kRenderQuantumFrames) {
-    // FIXME: handle multi-channel input and don't hard-code to stereo.
-    RefPtr<AudioBus> silence =
-        AudioBus::create(2, AudioUtilities::kRenderQuantumFrames);
-    m_inputFifo->push(silence.get());
-  }
 }
 
 AudioDestination::~AudioDestination() {
@@ -171,8 +156,7 @@
   return static_cast<float>(Platform::current()->audioHardwareOutputChannels());
 }
 
-void AudioDestination::render(const WebVector<float*>& sourceData,
-                              const WebVector<float*>& audioData,
+void AudioDestination::render(const WebVector<float*>& audioData,
                               size_t numberOfFrames,
                               double delay,
                               double delayTimestamp,
@@ -196,15 +180,6 @@
   m_outputPosition.timestamp = delayTimestamp;
   m_outputPositionReceivedTimestamp = base::TimeTicks::Now();
 
-  // Buffer optional live input.
-  if (sourceData.size() >= 2) {
-    // FIXME: handle multi-channel input and don't hard-code to stereo.
-    RefPtr<AudioBus> wrapperBus = AudioBus::create(2, numberOfFrames, false);
-    wrapperBus->setChannelMemory(0, sourceData[0], numberOfFrames);
-    wrapperBus->setChannelMemory(1, sourceData[1], numberOfFrames);
-    m_inputFifo->push(wrapperBus.get());
-  }
-
   for (unsigned i = 0; i < m_numberOfOutputChannels; ++i)
     m_renderBus->setChannelMemory(i, audioData[i], numberOfFrames);
 
@@ -214,12 +189,6 @@
 }
 
 void AudioDestination::provideInput(AudioBus* bus, size_t framesToProcess) {
-  AudioBus* sourceBus = nullptr;
-  if (m_inputFifo->framesInFifo() >= framesToProcess) {
-    m_inputFifo->consume(m_inputBus.get(), framesToProcess);
-    sourceBus = m_inputBus.get();
-  }
-
   AudioIOPosition outputPosition = m_outputPosition;
 
   // If platfrom buffer is more than two times longer than |framesToProcess|
@@ -237,7 +206,7 @@
   if (outputPosition.position < 0.0)
     outputPosition.position = 0.0;
 
-  m_callback.render(sourceBus, bus, framesToProcess, outputPosition);
+  m_callback.render(nullptr, bus, framesToProcess, outputPosition);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/audio/AudioDestination.h b/third_party/WebKit/Source/platform/audio/AudioDestination.h
index e986b82..f1d13c3 100644
--- a/third_party/WebKit/Source/platform/audio/AudioDestination.h
+++ b/third_party/WebKit/Source/platform/audio/AudioDestination.h
@@ -42,7 +42,6 @@
 
 namespace blink {
 
-class AudioFIFO;
 class AudioPullFIFO;
 class SecurityOrigin;
 
@@ -80,8 +79,7 @@
   float sampleRate() const { return m_sampleRate; }
 
   // WebAudioDevice::RenderCallback
-  void render(const WebVector<float*>& sourceData,
-              const WebVector<float*>& audioData,
+  void render(const WebVector<float*>& audioData,
               size_t numberOfFrames,
               double delay,
               double delayTimestamp,
@@ -105,14 +103,12 @@
  private:
   AudioIOCallback& m_callback;
   unsigned m_numberOfOutputChannels;
-  RefPtr<AudioBus> m_inputBus;
   RefPtr<AudioBus> m_renderBus;
   float m_sampleRate;
   bool m_isPlaying;
   std::unique_ptr<WebAudioDevice> m_audioDevice;
   size_t m_callbackBufferSize;
 
-  std::unique_ptr<AudioFIFO> m_inputFifo;
   std::unique_ptr<AudioPullFIFO> m_fifo;
 
   size_t m_framesElapsed;
diff --git a/third_party/WebKit/Source/platform/exported/WebAudioDevice.cpp b/third_party/WebKit/Source/platform/exported/WebAudioDevice.cpp
index 7b5aa7c..7994321d 100644
--- a/third_party/WebKit/Source/platform/exported/WebAudioDevice.cpp
+++ b/third_party/WebKit/Source/platform/exported/WebAudioDevice.cpp
@@ -27,7 +27,6 @@
 namespace blink {
 
 void WebAudioDevice::RenderCallback::render(
-    const WebVector<float*>& sourceData,
     const WebVector<float*>& destinationData,
     size_t numberOfFrames,
     double delay,
diff --git a/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp b/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp
index 036fc041..028055ba6 100644
--- a/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp
+++ b/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp
@@ -89,13 +89,13 @@
                                                 int32_t height,
                                                 float deviceScaleFactor) {
   if (!m_currentSurfaceId.is_valid() && surfaceId.is_valid()) {
+    // First time a SurfaceId is received
     m_currentSurfaceId = surfaceId;
     GraphicsLayer::unregisterContentsLayer(m_webLayer.get());
     m_webLayer->removeFromParent();
 
     scoped_refptr<cc::SurfaceLayer> surfaceLayer =
         cc::SurfaceLayer::Create(m_refFactory);
-    // TODO(xlai): Update this on resize.
     cc::SurfaceInfo info(surfaceId, deviceScaleFactor,
                          gfx::Size(width, height));
     surfaceLayer->SetSurfaceInfo(
@@ -106,9 +106,20 @@
         Platform::current()->compositorSupport()->createLayerFromCCLayer(
             m_CCLayer.get());
     GraphicsLayer::registerContentsLayer(m_webLayer.get());
-
-    m_observer->OnWebLayerReplaced();
+  } else if (m_currentSurfaceId != surfaceId) {
+    // A different SurfaceId is received, prompting change to existing
+    // SurfaceLayer
+    m_currentSurfaceId = surfaceId;
+    cc::SurfaceInfo info(m_currentSurfaceId, deviceScaleFactor,
+                         gfx::Size(width, height));
+    cc::SurfaceLayer* surfaceLayer =
+        static_cast<cc::SurfaceLayer*>(m_CCLayer.get());
+    surfaceLayer->SetSurfaceInfo(
+        info, true /* scale layer bounds with surface size */);
   }
+
+  m_observer->OnWebLayerReplaced();
+  m_CCLayer->SetBounds(gfx::Size(width, height));
 }
 
 void CanvasSurfaceLayerBridge::satisfyCallback(
diff --git a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp
index d3291101..61331f8 100644
--- a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp
+++ b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp
@@ -36,6 +36,7 @@
     : m_frameSinkId(cc::FrameSinkId(clientId, sinkId)),
       m_width(width),
       m_height(height),
+      m_changeSizeForNextCommit(false),
       m_nextResourceId(1u),
       m_binding(this),
       m_placeholderCanvasId(canvasId) {
@@ -357,6 +358,10 @@
       NOTREACHED();
   }
 
+  if (m_changeSizeForNextCommit) {
+    m_currentLocalFrameId = m_surfaceIdAllocator.GenerateId();
+    m_changeSizeForNextCommit = false;
+  }
   m_sink->SubmitCompositorFrame(m_currentLocalFrameId, std::move(frame));
 }
 
@@ -406,8 +411,11 @@
 }
 
 void OffscreenCanvasFrameDispatcherImpl::reshape(int width, int height) {
-  m_width = width;
-  m_height = height;
+  if (m_width != width || m_height != height) {
+    m_width = width;
+    m_height = height;
+    m_changeSizeForNextCommit = true;
+  }
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.h b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.h
index 084fd9d..bb531ca 100644
--- a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.h
+++ b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.h
@@ -61,6 +61,7 @@
 
   int m_width;
   int m_height;
+  bool m_changeSizeForNextCommit;
 
   unsigned m_nextResourceId;
   HashMap<unsigned, RefPtr<StaticBitmapImage>> m_cachedImages;
diff --git a/third_party/WebKit/Source/platform/heap/HeapAllocator.h b/third_party/WebKit/Source/platform/heap/HeapAllocator.h
index 004de05..e6c36a2 100644
--- a/third_party/WebKit/Source/platform/heap/HeapAllocator.h
+++ b/third_party/WebKit/Source/platform/heap/HeapAllocator.h
@@ -479,6 +479,17 @@
 };
 
 template <typename T>
+struct VectorTraits<blink::SameThreadCheckedMember<T>>
+    : VectorTraitsBase<blink::SameThreadCheckedMember<T>> {
+  STATIC_ONLY(VectorTraits);
+  static const bool needsDestruction = false;
+  static const bool canInitializeWithMemset = true;
+  static const bool canClearUnusedSlotsWithMemset = true;
+  static const bool canMoveWithMemcpy = true;
+  static const bool canSwapUsingCopyOrMove = false;
+};
+
+template <typename T>
 struct VectorTraits<blink::TraceWrapperMember<T>>
     : VectorTraitsBase<blink::TraceWrapperMember<T>> {
   STATIC_ONLY(VectorTraits);
@@ -603,6 +614,46 @@
 };
 
 template <typename T>
+struct HashTraits<blink::SameThreadCheckedMember<T>>
+    : SimpleClassHashTraits<blink::SameThreadCheckedMember<T>> {
+  STATIC_ONLY(HashTraits);
+  // FIXME: The distinction between PeekInType and PassInType is there for
+  // the sake of the reference counting handles. When they are gone the two
+  // types can be merged into PassInType.
+  // FIXME: Implement proper const'ness for iterator types. Requires support
+  // in the marking Visitor.
+  using PeekInType = T*;
+  using PassInType = T*;
+  using IteratorGetType = blink::SameThreadCheckedMember<T>*;
+  using IteratorConstGetType = const blink::SameThreadCheckedMember<T>*;
+  using IteratorReferenceType = blink::SameThreadCheckedMember<T>&;
+  using IteratorConstReferenceType = const blink::SameThreadCheckedMember<T>&;
+  static IteratorReferenceType getToReferenceConversion(IteratorGetType x) {
+    return *x;
+  }
+  static IteratorConstReferenceType getToReferenceConstConversion(
+      IteratorConstGetType x) {
+    return *x;
+  }
+
+  using PeekOutType = T*;
+
+  template <typename U>
+  static void store(const U& value,
+                    blink::SameThreadCheckedMember<T>& storage) {
+    storage = value;
+  }
+
+  static PeekOutType peek(const blink::SameThreadCheckedMember<T>& value) {
+    return value;
+  }
+
+  static blink::SameThreadCheckedMember<T> emptyValue() {
+    return blink::SameThreadCheckedMember<T>(nullptr, nullptr);
+  }
+};
+
+template <typename T>
 struct HashTraits<blink::TraceWrapperMember<T>>
     : SimpleClassHashTraits<blink::TraceWrapperMember<T>> {
   STATIC_ONLY(HashTraits);
diff --git a/third_party/WebKit/Source/platform/heap/Member.h b/third_party/WebKit/Source/platform/heap/Member.h
index aec34ac..1a6502d 100644
--- a/third_party/WebKit/Source/platform/heap/Member.h
+++ b/third_party/WebKit/Source/platform/heap/Member.h
@@ -252,6 +252,135 @@
   friend class Visitor;
 };
 
+// A checked version of Member<>, verifying that only same-thread references
+// are kept in the smart pointer. Intended to be used to diagnose unclean
+// thread reference usage in release builds. It simply exposes the debug-only
+// MemberBase<> checking we already have in place for select usage to diagnose
+// per-thread issues. Only intended used temporarily while diagnosing suspected
+// problems with cross-thread references.
+template <typename T>
+class SameThreadCheckedMember : public Member<T> {
+  DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+  typedef Member<T> Parent;
+
+ public:
+  SameThreadCheckedMember() : Parent() { saveCreationThreadState(); }
+  SameThreadCheckedMember(std::nullptr_t) : Parent(nullptr) {
+    saveCreationThreadState();
+  }
+
+  SameThreadCheckedMember(T* raw) : Parent(raw) {
+    saveCreationThreadState();
+    checkPointer();
+  }
+
+  SameThreadCheckedMember(T& raw) : Parent(raw) {
+    saveCreationThreadState();
+    checkPointer();
+  }
+
+  SameThreadCheckedMember(WTF::HashTableDeletedValueType x) : Parent(x) {
+    saveCreationThreadState();
+    checkPointer();
+  }
+
+  SameThreadCheckedMember(const SameThreadCheckedMember& other)
+      : Parent(other) {
+    saveCreationThreadState();
+  }
+  template <typename U>
+  SameThreadCheckedMember(const SameThreadCheckedMember<U>& other)
+      : Parent(other) {
+    saveCreationThreadState();
+    checkPointer();
+  }
+
+  template <typename U>
+  SameThreadCheckedMember(const Persistent<U>& other) : Parent(other) {
+    saveCreationThreadState();
+    checkPointer();
+  }
+
+  template <typename U>
+  SameThreadCheckedMember& operator=(const Persistent<U>& other) {
+    Parent::operator=(other);
+    checkPointer();
+    return *this;
+  }
+
+  template <typename U>
+  SameThreadCheckedMember& operator=(const SameThreadCheckedMember<U>& other) {
+    Parent::operator=(other);
+    checkPointer();
+    return *this;
+  }
+
+  template <typename U>
+  SameThreadCheckedMember& operator=(const WeakMember<U>& other) {
+    Parent::operator=(other);
+    checkPointer();
+    return *this;
+  }
+
+  template <typename U>
+  SameThreadCheckedMember& operator=(U* other) {
+    Parent::operator=(other);
+    checkPointer();
+    return *this;
+  }
+
+  SameThreadCheckedMember& operator=(std::nullptr_t) {
+    Parent::operator=(nullptr);
+    return *this;
+  }
+
+ protected:
+  template <bool x,
+            WTF::WeakHandlingFlag y,
+            WTF::ShouldWeakPointersBeMarkedStrongly z,
+            typename U,
+            typename V>
+  friend struct CollectionBackingTraceTrait;
+  friend class Visitor;
+
+ private:
+  void checkPointer() {
+    if (!this->m_raw)
+      return;
+    // HashTable can store a special value (which is not aligned to the
+    // allocation granularity) to Member<> to represent a deleted entry.
+    // Thus we treat a pointer that is not aligned to the granularity
+    // as a valid pointer.
+    if (reinterpret_cast<intptr_t>(this->m_raw) % allocationGranularity)
+      return;
+
+    ThreadState* current = ThreadState::current();
+    DCHECK(current);
+    // m_creationThreadState may be null when this is used in a heap
+    // collection which initialized the Member with memset and the
+    // constructor wasn't called.
+    if (m_creationThreadState) {
+      // Member should point to objects that belong in the same ThreadHeap.
+      CHECK_EQ(&ThreadState::fromObject(this->m_raw)->heap(),
+               &m_creationThreadState->heap());
+      // Member should point to objects that belong in the same ThreadHeap.
+      CHECK_EQ(&current->heap(), &m_creationThreadState->heap());
+    } else {
+      CHECK_EQ(&ThreadState::fromObject(this->m_raw)->heap(), &current->heap());
+    }
+  }
+
+  void saveCreationThreadState() {
+    m_creationThreadState = ThreadState::current();
+    // All Members should be created in an attached thread, but an empty
+    // value Member may be created on an unattached thread by a heap
+    // collection iterator.
+    CHECK(this->m_creationThreadState || !this->m_raw);
+  }
+
+  const ThreadState* m_creationThreadState;
+};
+
 // WeakMember is similar to Member in that it is used to point to other oilpan
 // heap allocated objects.
 // However instead of creating a strong pointer to the object, the WeakMember
@@ -403,6 +532,12 @@
 };
 
 template <typename T>
+struct DefaultHash<blink::SameThreadCheckedMember<T>> {
+  STATIC_ONLY(DefaultHash);
+  using Hash = MemberHash<T>;
+};
+
+template <typename T>
 struct DefaultHash<blink::TraceWrapperMember<T>> {
   STATIC_ONLY(DefaultHash);
   using Hash = MemberHash<T>;
@@ -427,6 +562,12 @@
 };
 
 template <typename T>
+struct IsTraceable<blink::SameThreadCheckedMember<T>> {
+  STATIC_ONLY(IsTraceable);
+  static const bool value = true;
+};
+
+template <typename T>
 struct IsTraceable<blink::TraceWrapperMember<T>> {
   STATIC_ONLY(IsTraceable);
   static const bool value = true;
diff --git a/third_party/WebKit/Source/platform/heap/ThreadingTraits.h b/third_party/WebKit/Source/platform/heap/ThreadingTraits.h
index 7dfffb55..a58b796 100644
--- a/third_party/WebKit/Source/platform/heap/ThreadingTraits.h
+++ b/third_party/WebKit/Source/platform/heap/ThreadingTraits.h
@@ -19,6 +19,8 @@
 namespace blink {
 
 template <typename T>
+class SameThreadCheckedMember;
+template <typename T>
 class TraceWrapperMember;
 
 // ThreadAffinity indicates which threads objects can be used on. We
@@ -83,6 +85,12 @@
 };
 
 template <typename T>
+struct ThreadingTrait<SameThreadCheckedMember<T>> {
+  STATIC_ONLY(ThreadingTrait);
+  static const ThreadAffinity Affinity = ThreadingTrait<T>::Affinity;
+};
+
+template <typename T>
 struct ThreadingTrait<TraceWrapperMember<T>> {
   STATIC_ONLY(ThreadingTrait);
   static const ThreadAffinity Affinity = ThreadingTrait<T>::Affinity;
diff --git a/third_party/WebKit/Source/platform/heap/TraceTraits.h b/third_party/WebKit/Source/platform/heap/TraceTraits.h
index 8e37a659..01bfdd9 100644
--- a/third_party/WebKit/Source/platform/heap/TraceTraits.h
+++ b/third_party/WebKit/Source/platform/heap/TraceTraits.h
@@ -395,6 +395,14 @@
 };
 
 template <typename T>
+class TraceEagerlyTrait<SameThreadCheckedMember<T>> {
+  STATIC_ONLY(TraceEagerlyTrait);
+
+ public:
+  static const bool value = TraceEagerlyTrait<T>::value;
+};
+
+template <typename T>
 class TraceEagerlyTrait<TraceWrapperMember<T>> {
   STATIC_ONLY(TraceEagerlyTrait);
 
diff --git a/third_party/WebKit/Source/platform/heap/Visitor.h b/third_party/WebKit/Source/platform/heap/Visitor.h
index 6b8c13a6..82ea433 100644
--- a/third_party/WebKit/Source/platform/heap/Visitor.h
+++ b/third_party/WebKit/Source/platform/heap/Visitor.h
@@ -51,6 +51,8 @@
 class ThreadState;
 class Visitor;
 template <typename T>
+class SameThreadCheckedMember;
+template <typename T>
 class TraceWrapperMember;
 
 // The TraceMethodDelegate is used to convert a trace method for type T to a
@@ -163,6 +165,11 @@
     trace(*(static_cast<const Member<T>*>(&t)));
   }
 
+  template <typename T>
+  void trace(const SameThreadCheckedMember<T>& t) {
+    trace(*(static_cast<const Member<T>*>(&t)));
+  }
+
   // Fallback method used only when we need to trace raw pointers of T.
   // This is the case when a member is a union where we do not support members.
   template <typename T>
diff --git a/third_party/WebKit/Source/platform/scroll/Scrollbar.h b/third_party/WebKit/Source/platform/scroll/Scrollbar.h
index 95caaccf..a9cf98a 100644
--- a/third_party/WebKit/Source/platform/scroll/Scrollbar.h
+++ b/third_party/WebKit/Source/platform/scroll/Scrollbar.h
@@ -110,6 +110,7 @@
 
   // This returns device-scale-factor-aware pixel value.
   // e.g. 15 in dsf=1.0, 30 in dsf=2.0.
+  // This returns 0 for overlay scrollbars.
   // See also ScrolbarTheme::scrollbatThickness().
   int scrollbarThickness() const;
 
diff --git a/third_party/WebKit/Source/platform/scroll/ScrollbarTheme.h b/third_party/WebKit/Source/platform/scroll/ScrollbarTheme.h
index ec03635..9036cd2 100644
--- a/third_party/WebKit/Source/platform/scroll/ScrollbarTheme.h
+++ b/third_party/WebKit/Source/platform/scroll/ScrollbarTheme.h
@@ -59,6 +59,8 @@
   virtual ScrollbarPart hitTest(const ScrollbarThemeClient&, const IntPoint&);
 
   // This returns a fixed value regardless of device-scale-factor.
+  // This returns thickness when scrollbar is painted.  i.e. It's not 0 even in
+  // overlay scrollbar mode.
   // See also Scrollbar::scrollbarThickness().
   virtual int scrollbarThickness(ScrollbarControlSize = RegularScrollbar) {
     return 0;
diff --git a/third_party/WebKit/public/platform/WebAudioDevice.h b/third_party/WebKit/public/platform/WebAudioDevice.h
index 1de0eba2..d161d5b6 100644
--- a/third_party/WebKit/public/platform/WebAudioDevice.h
+++ b/third_party/WebKit/public/platform/WebAudioDevice.h
@@ -43,8 +43,7 @@
     // measurements of the state of the system in the recent past. To be clear,
     // |delay| does *not* represent the point-in-time at which the first
     // rendered sample will be played out.
-    virtual void render(const WebVector<float*>& sourceData,
-                        const WebVector<float*>& destinationData,
+    virtual void render(const WebVector<float*>& destinationData,
                         size_t numberOfFrames,
                         double delay,           // Output delay in seconds.
                         double delayTimestamp,  // System timestamp in seconds
diff --git a/tools/perf/OWNERS b/tools/perf/OWNERS
index 8a620f3..ac6816ed 100644
--- a/tools/perf/OWNERS
+++ b/tools/perf/OWNERS
@@ -11,8 +11,8 @@
 # Simple rubberstamp-only oilpan-related changes.
 peria@chromium.org
 
-# For system health & memory-infra related changes.
-petrcermak@chromium.org
+# For system health memory benchmarks & memory-infra related changes.
+perezju@chromium.org
 
 # For page_cycler and loading benchmarks related changes.
 kouhei@chromium.org
@@ -20,5 +20,6 @@
 # emeritus:
 # marja@chromium.org
 # nduca@chromium.org
+# petrcermak@chromium.org
 # qyearsley@chromium.org
 # tonyg@chromium.org
diff --git a/tools/perf/page_sets/system_health/blank_stories.py b/tools/perf/page_sets/system_health/blank_stories.py
index dbfd385d..ffe0ecc 100644
--- a/tools/perf/page_sets/system_health/blank_stories.py
+++ b/tools/perf/page_sets/system_health/blank_stories.py
@@ -4,9 +4,7 @@
 
 from page_sets.system_health import system_health_story
 
-from telemetry import decorators
 
-@decorators.Disabled('win') # crbug.com/656040
 class BlankAboutBlankStory(system_health_story.SystemHealthStory):
   """Story that loads the about:blank page."""
 
diff --git a/tools/perf/page_sets/system_health/browsing_stories.py b/tools/perf/page_sets/system_health/browsing_stories.py
index 50d7779..04862001 100644
--- a/tools/perf/page_sets/system_health/browsing_stories.py
+++ b/tools/perf/page_sets/system_health/browsing_stories.py
@@ -87,10 +87,7 @@
         repeat_count=self.MAIN_PAGE_SCROLL_REPEAT)
 
 
-# TODO(ulan): Enable this story on mobile once it uses less memory and does not
-# crash with OOM.
-@decorators.Disabled('android',
-                     'win') # crbug.com/665465
+@decorators.Disabled('android')  # crbug.com/676338
 class CnnStory(_NewsBrowsingStory):
   """The second top website in http://www.alexa.com/topsites/category/News"""
   NAME = 'browse:news:cnn'
diff --git a/tools/perf/page_sets/system_health/searching_stories.py b/tools/perf/page_sets/system_health/searching_stories.py
index d63cf0740..898aacf0 100644
--- a/tools/perf/page_sets/system_health/searching_stories.py
+++ b/tools/perf/page_sets/system_health/searching_stories.py
@@ -4,10 +4,7 @@
 
 from page_sets.system_health import system_health_story
 
-from telemetry import decorators
 
-
-@decorators.Disabled('win')  # http://crbug.com/642463
 class SearchGoogleStory(system_health_story.SystemHealthStory):
   NAME = 'search:portal:google'
   URL = 'https://www.google.co.uk/'
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index b713b21..a9d9699 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -155,7 +155,6 @@
     "//skia",
     "//ui/base",
     "//ui/base/ime",
-    "//ui/compositor",
     "//ui/display",
     "//ui/events",
     "//ui/events:events_base",
@@ -168,6 +167,10 @@
     "//ui/platform_window/stub",
   ]
 
+  public_deps = [
+    "//ui/compositor",
+  ]
+
   data_deps = [
     "//services/ui",
   ]
diff --git a/ui/display/BUILD.gn b/ui/display/BUILD.gn
index c6031d6..600eef16 100644
--- a/ui/display/BUILD.gn
+++ b/ui/display/BUILD.gn
@@ -132,6 +132,8 @@
 test("display_unittests") {
   sources = [
     "display_change_notifier_unittest.cc",
+    "display_layout_builder_unittest.cc",
+    "display_layout_unittest.cc",
     "display_list_unittest.cc",
     "display_unittest.cc",
     "fake_display_snapshot_unittests.cc",
@@ -142,8 +144,6 @@
     "manager/chromeos/query_content_protection_task_unittest.cc",
     "manager/chromeos/touchscreen_util_unittest.cc",
     "manager/chromeos/update_display_configuration_task_unittest.cc",
-    "manager/display_layout_builder_unittest.cc",
-    "manager/display_layout_unittest.cc",
     "manager/display_manager_utilities_unittest.cc",
     "manager/managed_display_info_unittest.cc",
     "screen_unittest.cc",
diff --git a/ui/display/manager/display_layout_builder_unittest.cc b/ui/display/display_layout_builder_unittest.cc
similarity index 100%
rename from ui/display/manager/display_layout_builder_unittest.cc
rename to ui/display/display_layout_builder_unittest.cc
diff --git a/ui/display/manager/display_layout_unittest.cc b/ui/display/display_layout_unittest.cc
similarity index 100%
rename from ui/display/manager/display_layout_unittest.cc
rename to ui/display/display_layout_unittest.cc
diff --git a/ui/gfx/blit.cc b/ui/gfx/blit.cc
index 169eae2..614f6c9 100644
--- a/ui/gfx/blit.cc
+++ b/ui/gfx/blit.cc
@@ -71,25 +71,10 @@
                   const gfx::Rect& in_clip,
                   const gfx::Vector2d& offset) {
   DCHECK(!HasClipOrTransform(*canvas));  // Don't support special stuff.
-#if defined(OS_WIN)
-  // If we have a PlatformCanvas, we should use ScrollDC. Otherwise, fall
-  // through to the software implementation.
-  if (skia::SupportsPlatformPaint(canvas)) {
-    skia::ScopedPlatformPaint scoped_platform_paint(canvas);
-    HDC hdc = scoped_platform_paint.GetNativeDrawingContext();
 
-    RECT damaged_rect;
-    RECT r = in_clip.ToRECT();
-    ScrollDC(hdc, offset.x(), offset.y(), NULL, &r, NULL, &damaged_rect);
-    return;
-  }
-#endif  // defined(OS_WIN)
-  // For non-windows, always do scrolling in software.
-  // Cairo has no nice scroll function so we do our own. On Mac it's possible to
-  // use platform scroll code, but it's complex so we just use the same path
-  // here. Either way it will be software-only, so it shouldn't matter much.
   SkPixmap pixmap;
-  skia::GetWritablePixels(canvas, &pixmap);
+  bool success = skia::GetWritablePixels(canvas, &pixmap);
+  DCHECK(success);
 
   // We expect all coords to be inside the canvas, so clip here.
   gfx::Rect clip = gfx::IntersectRects(
diff --git a/ui/login/display_manager.js b/ui/login/display_manager.js
index a3b5210c..5ca41e2f 100644
--- a/ui/login/display_manager.js
+++ b/ui/login/display_manager.js
@@ -344,7 +344,10 @@
                 currentStepId) != -1) {
           chrome.send('toggleEnableDebuggingScreen');
         }
-      } else if (name == ACCELERATOR_ENROLLMENT) {
+      } else if (name == ACCELERATOR_ENROLLMENT ||
+                 name == ACCELERATOR_ENROLLMENT_AD) {
+        if (name == ACCELERATOR_ENROLLMENT_AD)
+          chrome.send('toggleEnrollmentAd');
         if (currentStepId == SCREEN_GAIA_SIGNIN ||
             currentStepId == SCREEN_ACCOUNT_PICKER) {
           chrome.send('toggleEnrollmentScreen');
@@ -354,11 +357,6 @@
           // proceed straight to enrollment screen when EULA is accepted.
           chrome.send('skipUpdateEnrollAfterEula');
         }
-      } else if (name == ACCELERATOR_ENROLLMENT_AD) {
-        if (currentStepId == SCREEN_GAIA_SIGNIN ||
-            currentStepId == SCREEN_ACCOUNT_PICKER) {
-          chrome.send('toggleEnrollmentAd');
-        }
       } else if (name == ACCELERATOR_KIOSK_ENABLE) {
         if (currentStepId == SCREEN_GAIA_SIGNIN ||
             currentStepId == SCREEN_ACCOUNT_PICKER) {
diff --git a/ui/ozone/platform/drm/common/drm_util.cc b/ui/ozone/platform/drm/common/drm_util.cc
index e3b55cb..1de26fd7 100644
--- a/ui/ozone/platform/drm/common/drm_util.cc
+++ b/ui/ozone/platform/drm/common/drm_util.cc
@@ -10,10 +10,8 @@
 #include <sys/mman.h>
 #include <xf86drm.h>
 #include <xf86drmMode.h>
-#include <algorithm>
 #include <utility>
 
-#include "base/containers/small_map.h"
 #include "ui/display/util/edid_parser.h"
 
 #if !defined(DRM_MODE_CONNECTOR_DSI)
@@ -58,21 +56,16 @@
   return false;
 }
 
-// Return a CRTC compatible with |connector| and not already used in |displays|.
-// If there are multiple compatible CRTCs, the one that supports the majority of
-// planes will be returned.
 uint32_t GetCrtc(int fd,
                  drmModeConnector* connector,
                  drmModeRes* resources,
                  const ScopedVector<HardwareDisplayControllerInfo>& displays) {
-  ScopedDrmPlaneResPtr plane_resources(drmModeGetPlaneResources(fd));
-  std::vector<ScopedDrmPlanePtr> planes;
-  for (uint32_t i = 0; i < plane_resources->count_planes; i++)
-    planes.emplace_back(drmModeGetPlane(fd, plane_resources->planes[i]));
-
-  DCHECK_GE(32, resources->count_crtcs);
-  uint32_t best_crtc = 0;
-  int best_crtc_planes = 0;
+  // If the connector already has an encoder try to re-use.
+  if (connector->encoder_id) {
+    ScopedDrmEncoderPtr encoder(drmModeGetEncoder(fd, connector->encoder_id));
+    if (encoder && encoder->crtc_id && !IsCrtcInUse(encoder->crtc_id, displays))
+      return encoder->crtc_id;
+  }
 
   // Try to find an encoder for the connector.
   for (int i = 0; i < connector->count_encoders; ++i) {
@@ -82,24 +75,15 @@
 
     for (int j = 0; j < resources->count_crtcs; ++j) {
       // Check if the encoder is compatible with this CRTC
-      int crtc_bit = 1 << j;
-      if (!(encoder->possible_crtcs & crtc_bit) ||
+      if (!(encoder->possible_crtcs & (1 << j)) ||
           IsCrtcInUse(resources->crtcs[j], displays))
         continue;
 
-      int supported_planes = std::count_if(
-          planes.begin(), planes.end(), [crtc_bit](const ScopedDrmPlanePtr& p) {
-            return p->possible_crtcs & crtc_bit;
-          });
-
-      if (supported_planes > best_crtc_planes) {
-        best_crtc_planes = supported_planes;
-        best_crtc = resources->crtcs[j];
-      }
+      return resources->crtcs[j];
     }
   }
 
-  return best_crtc;
+  return 0;
 }
 
 // Computes the refresh rate for the specific mode. If we have enough
@@ -242,54 +226,21 @@
   DCHECK(resources) << "Failed to get DRM resources";
   ScopedVector<HardwareDisplayControllerInfo> displays;
 
-  std::vector<ScopedDrmConnectorPtr> available_connectors;
-  std::vector<ScopedDrmConnectorPtr::element_type*> connectors;
   for (int i = 0; i < resources->count_connectors; ++i) {
     ScopedDrmConnectorPtr connector(
         drmModeGetConnector(fd, resources->connectors[i]));
-    connectors.push_back(connector.get());
 
-    if (connector && connector->connection == DRM_MODE_CONNECTED &&
-        connector->count_modes != 0)
-      available_connectors.push_back(std::move(connector));
-  }
+    if (!connector || connector->connection != DRM_MODE_CONNECTED ||
+        connector->count_modes == 0)
+      continue;
 
-  base::SmallMap<std::map<ScopedDrmConnectorPtr::element_type*, int>>
-      connector_crtcs;
-  for (auto& c : available_connectors) {
-    uint32_t possible_crtcs = 0;
-    for (int i = 0; i < c->count_encoders; ++i) {
-      ScopedDrmEncoderPtr encoder(drmModeGetEncoder(fd, c->encoders[i]));
-      if (!encoder)
-        continue;
-      possible_crtcs |= encoder->possible_crtcs;
-    }
-    connector_crtcs[c.get()] = possible_crtcs;
-  }
-  // Make sure to start assigning a crtc to the connector that supports the
-  // fewest crtcs first.
-  std::stable_sort(available_connectors.begin(), available_connectors.end(),
-                   [&connector_crtcs](const ScopedDrmConnectorPtr& c1,
-                                      const ScopedDrmConnectorPtr& c2) {
-                     // When c1 supports a proper subset of the crtcs of c2, we
-                     // should process c1 first (return true).
-                     int c1_crtcs = connector_crtcs[c1.get()];
-                     int c2_crtcs = connector_crtcs[c2.get()];
-                     return (c1_crtcs & c2_crtcs) == c1_crtcs &&
-                            c1_crtcs != c2_crtcs;
-                   });
-
-  for (auto& c : available_connectors) {
-    uint32_t crtc_id = GetCrtc(fd, c.get(), resources.get(), displays);
+    uint32_t crtc_id = GetCrtc(fd, connector.get(), resources.get(), displays);
     if (!crtc_id)
       continue;
 
     ScopedDrmCrtcPtr crtc(drmModeGetCrtc(fd, crtc_id));
-    size_t index = std::find(connectors.begin(), connectors.end(), c.get()) -
-                   connectors.begin();
-    DCHECK_LT(index, connectors.size());
-    displays.push_back(new HardwareDisplayControllerInfo(
-        std::move(c), std::move(crtc), index));
+    displays.push_back(new HardwareDisplayControllerInfo(std::move(connector),
+                                                         std::move(crtc), i));
   }
 
   return displays;