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"><a is="action-link" id="android-apps-settings-link" role="link"></ph>preferences<ph name="END_LINK"></a></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, ¤t_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, ¶m_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(¤t->heap(), &m_creationThreadState->heap()); + } else { + CHECK_EQ(&ThreadState::fromObject(this->m_raw)->heap(), ¤t->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;